问题描述:
N个人围成一圈抛球,初始状态下第一个人持球,同时每个人都有概率将球传左或传右,概率给出。
当每个人都至少接到过一次传球后游戏结束,最后一个接到球的人取胜。
问题转化:
给定一个规模为N-1的数组,其中元素表示每个人(不包括第N个)右传球的概率。
初始状态下第k个人持球(与上问题等效)
解法:
首先找到该问题中的一个子问题:
已知AB两人相邻,A右投概率为PA,B右投概率为PB,则球左入左出的概率是多少?
进一步,当A1,A2,...,Ax,B1,B2,...,By个人相邻,则球左入左出概率是多少?
对上述数学问题的解是该算法实现的核心
抽象为数学问题:
对任意一个概率列{Pn},其实我们只关心四个数据:
左进左出概率,左进右出概率,右进左出概率,右进右出概率。
换而言之,任何概率列只要知道了这四个数据,我们就可以对其进行计算。
故此定义区间{Pn}对应的元组M为2*2矩阵 为方便起见,转置如下
左进左出 右进左出 M11 M21
左进右出 右进右出 M12 M22
则可定义元组加法 C = A + B
解决问题:
显然有 C11 = A11 + A12*B11*A21 + A12*(B11*A22)*B11*A21 + A12*(B11*A22)*(B11*A22)*B11*A21 + ...
即 C11 = A11 + A12*B11*A21 * (1+(B11*A22)+(B11*A22)^2+...)
根据等比数列求和,令n->∞,则 C11 = A11 + A12*B11*A21/(1-B11A22)
类似可求C22,则C12=1-C11,C21=1-C22全部可求
然后应用模型:
第N个人最后接到球,首先分为两种可能:从第一个人传过来,或者从第N-1个人传过来。
不妨设从第一个人传过来,由于此前不能接到球,因此概率区间为[0,N-1],其对应的元组可求。
但球的初始状态并不在"左进"或"右进"状态,元组中并不包含相应的数据。
假设球在第一个人手中,就形成了"左进"状态,只要求出第一个人拿到球的概率,再乘以左进左出概率就行。
递归的,为了求出第一个人拿到球的概率,只要求出第二个人拿到球的概率,再乘以[1,N-1]区间对应的左进左出概率。
直到第k个,由于k本来就有球,此概率为1,递归终止。
不妨令Ri表示[i,N-1]区间的左进左出概率,显然p=R1*R2*...*Rk
通过上述分析,我们成功将"球最终从某一侧落入第N个人手中"的概率求了出来。
不难判断,两侧概率相加等于1
但这并不是我们想要的,我们想要的是"最后一个拿到球"
也就是说,在上述R1,R2,...,Rk中,至少有一个要满足右投到第N-1项,当然单个项满足并不难算,但至少一个怎么求?
很自然想到补集的方法。
令R1,R2,...,Rk全都不满足右投达到过第N-1项,此时的概率可表述为R1'*R2'*...*Rk'
其中Ri'为[i,N-2]区间的左进左出概率。
前者减后者即为所求。
如法炮制解决右传概率。
代码见下
# 测试用例
def getData():
return 2, [0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
# 由问题解法描述的概率列加和函数
def pad(A11,A12,A21,A22, B11,B12,B21,B22):
Cq = 1/(1-A22*B11)
Aq = A12 * A21 * Cq
Bq = B12 * B21 * Cq
return (
A11+B11*Aq,
A12-B11*Aq,
B21-A22*Bq,
B22+A22*Bq
)
def g(p):
return (1-p, p, 1-p, p)
# 初始化
k, data = getData()
# 1.1 求右区间左进左出概率
dp = (0,0,0,0)
dpm = (0,0,0,0)
if k<len(data):
# R-变量
dpm = g(data[k])
# R-区间
for p in data[k+1:-1]:
dpm = pad(*dpm, *g(p))
#print(dpm)
# R区间
dp = pad(*dpm, *g(data[-1]))
else:
# 否则dpm为零,dp为k的值
dp = g(data[k])
# 1.2 求R区间左出概率在左半部分的连乘积
tp = dp[0]
tpm = dpm[0]
for p in reversed(data[0:k]):
dp = pad(*g(p), *dp)
dpm = pad(*g(p), *dpm)
tp *= dp[0]
tpm *= dpm[0]
R = tp - tpm
# 2.1 求左区间右进右出概率
dp = (0,0,0,0)
dpm = (0,0,0,0)
if k>0:
# L-变量
dpm = g(data[k])
# L-区间
for p in reversed(data[1:k]):
dpm = pad(*g(p), *dpm)
#print(dpm)
# L区间
dp = pad(*g(data[0]), *dpm)
else:
# 否则dpm为零,dp为k的值
dp = g(data[k])
# 2.2 求L区间右出概率在右半部分的连乘积
tp = dp[0]
tpm = dpm[0]
for p in data[k+1:]:
dp = pad(*dp, *g(p))
dpm = pad(*dpm, *g(p))
tp *= dp[0]
tpm *= dpm[0]
L = tp - tpm
print(R, L, R+L)