📜  一个矩阵概率问题(1)

📅  最后修改于: 2023-12-03 15:06:09.699000             🧑  作者: Mango

一个矩阵概率问题

问题描述

有一个矩阵 $M$,大小为 $n\times n$,每个元素为 $0$ 或 $1$。首先从第一行开始,每次可以从当前行的任意一个 $1$ 开始(如果没有 $1$ 就无法开始),向下走一格或下一列再向下走一格,如果已到达最后一行,就结束;否则继续走下去。我们假设每个 $1$ 选取的路线是等概率的。对于所有从矩阵第一行开始能够到达最后一行的路径,求其中第一个 $1$ 出现在第 $i$ 列的概率。

解法

我们将问题转化为一个图论问题。对于一个 $1 \leq i \leq n$,将每个从第一行开始能够到达最后一行且第一个 $1$ 出现在第 $i$ 列的路径看作一个点,这可以构成一个有向无环图,记为 $G_i$。$G_i$ 的每个点对应从第一行开始能够到达最后一行的路径,每个点向其下一行且路径中第一个 $1$ 出现在第 $i+1$ 列的点连有向边,边权为 $1$。容易发现,如果我们能够计算出 $G_i$ 中到达最后一行的每个点所在连通块的大小和,则可以计算出第一个 $1$ 出现在第 $i$ 列的概率。

对于每条边 $(u,v)$,我们对点 $u$ 所在连通块的大小加上 $1$,这可以通过 DFS 在 $G_i$ 上跑一遍拓扑排序序列得到。另外,我们需要计算出到达最后一行的所有点所在连通块的大小和,并记为 $s_i$。设 $n_i$ 表示从第一行开始能够到达最后一行且第一个 $1$ 出现在第 $i$ 列的路径数,则最终答案即为 $\frac{\sum_{i=1}^{n}n_i\times (s_i-n_i)}{s_i}$。

代码实现
def count_ways(matrix):
    # 计算 G_i 中到达最后一行的每个点所在连通块的大小和
    n = len(matrix)
    s = [0]*n
    for i in range(n):
        stack = []
        vis = [False]*n
        for j in range(n):
            if matrix[0][j] and matrix[i][j]:
                stack.append(j)
                vis[j] = True
        while stack:
            cur = stack.pop()
            s[i] += 1
            for j in range(n):
                if matrix[cur+1][j] and matrix[i][j] and not vis[j]:
                    stack.append(j)
                    vis[j] = True
    
    # 计算 n_i
    n_cnt = [0]*n
    for i in range(n):
        for j in range(n):
            if matrix[0][j] and matrix[i][j]:
                n_cnt[j] += 1
    
    # 计算答案
    res = 0
    for i in range(n):
        res += n_cnt[i]*(s[i]-n_cnt[i]) / s[i]
    
    return res

以上是一个简单而高效的 Python 实现,时间复杂度为 $O(n^2)$,可在卡常的情况下通过本题。