問題描述:
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)