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

 问题描述:
    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 · 访问量 8815

猜你喜欢

转载自blog.csdn.net/qq_26946497/article/details/103918146