一道精彩的算法題(概率題)

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

 

發佈了11 篇原創文章 · 獲贊 8 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章