📌  相关文章
📜  计算长度为 N 的可能的二进制字符串,没有 P 个连续的 0 和 Q 个连续的 1(1)

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

计算长度为N的可能的二进制字符串,没有P个连续的0和Q个连续的1

在二进制字符串中有时需要限制一些特定的规则,例如在一个长度为N的二进制字符串中要求没有P个连续的0和Q个连续的1。我们可以通过动态规划和矩阵快速幂的方法来解决这个问题。

动态规划

使用 dp(i, j, 0/1) 来表示当前已生成长度为 i 的字符串,并且以 j 个连续的 0 或者 1 结尾的方案数。具体地,设 f[i][0/1] 表示只考虑前i位,最后一位为0/1的符合要求字符串数量,g[i][0/1] 表示只考虑前i位,最后两位为00/01或者10/11的符合要求字符串数量。

转移:

当最后一位为0时,f[i][0] = f[i-1][1]+g[i-1][1],表示上一位是 1 或者 01/11,还可以由上一个为01/11转移。

当最后一位为1时,f[i][1] = f[i-1][0]+g[i-1][0],表示上一位是 0 或者 10/00,还可以由上一个为10/00转移。

g[i][0] = f[i-1][0]

g[i][1] = f[i-1][1]

最终要求的是长度为 N 的方案数,所以需要将所有长度为 N 的方案数加起来即可。

时间复杂度为 $O(N)$。

矩阵快速幂

假设我们已经知道了长度为 n 的二进制字符串没有 p 个连续的 0 和 q 个连续的 1 的数量,我们如何计算长度为 $2n$ 的字符串数量呢?

首先,将长度为 $n$ 的所有合法字符串看做一个节点,建立一个长度为 $2n$ 的图,从一个长度为 $n$ 的节点向另一个长度为 $n$ 的节点连边当且仅当这两个节点对应字符串的后 $n-1$ 位相同且最后一位不相同,并且这两个节点对应的字符串长度为 $n-1$ 的前缀中不包含 p 个连续的 0 和 q 个连续的 1。

例如,当 n = 3,p = 2,q = 1 时,节点分别为:

000,001,010,011,100,101,110,111

合法的边连接:

000 -> 001,010,100

001 -> 010,100,110

010 -> 001,100,101

011 -> 101

100 -> 001,010,110

101 -> 010,110,111

110 -> 011,101,111

111 -> 011,101,110

可以看出这个图是一个 DAG(有向无环图),我们需要求出这个图的某一深度的点数,使用矩阵快速幂来解决这个问题。

具体地,设邻接矩阵为 A,其中 A[i][j] = 1 表示从字符串 i 可以到达字符串 j,由于这是一个 DAG,所以邻接矩阵是一个上三角矩阵,将其转化成下三角矩阵方便计算。

设矩阵 B 为一个 n*1 的向量,其中 B[i][0] 表示长度为 $n$ 的,以 $i$ 结尾的合法字符串数量。

根据定义,初始时有:

$$ B[i][0] = \begin{cases} 1 & i是合法字符串 \ 0 & i不是合法字符串 \ \end{cases} $$

那么长度为 $2n$ 的字符串的数量可以表示为 AB,其中 A 为邻接矩阵,B 为长度为 $n$ 的合法字符串数量的向量,即:

$$ A = \begin{bmatrix} 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \ 1 & 0 & 1 & 1 & 0 & 0 & 0 & 0 \ 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 \ 0 & 0 & 1 & 0 & 0 & 1 & 0 & 1 \ 1 & 1 & 0 & 1 & 0 & 0 & 1 & 0 \ 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 \ 0 & 1 & 0 & 1 & 1 & 0 & 0 & 0 \ 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 \ \end{bmatrix} $$

根据定义,结果为:

$$ B' = AB $$

时间复杂度为 $O(n^3\log N)$。

代码

动态规划的代码片段如下:

f = [[0] * 2 for i in range(N + 1)]
g = [[0] * 2 for i in range(N + 1)]
f[1][1] = f[1][0] = g[1][1] = g[1][0] = 1

for i in range(2, N+1):
    f[i][0] = f[i - 1][1] + g[i - 1][1]
    f[i][1] = f[i - 1][0] + g[i - 1][0]
    g[i][0] = f[i - 1][0]
    g[i][1] = f[i - 1][1]

ans = sum(f[N]) - (p == 0)  # 若p为0,则不需要减去以0结尾的方案数

return ans

矩阵快速幂的代码片段如下:

def matrix_multiply(A, B):
    n, m, p = len(A), len(A[0]), len(B[0])
    C = [[0] * p for i in range(n)]
    for i in range(n):
        for j in range(p):
            for k in range(m):
                C[i][j] += A[i][k] * B[k][j]
    return C

# 计算 A 的幂次
def matrix_power(A, k):
    n = len(A)
    res = [[0] * n for i in range(n)]
    for i in range(n):
        res[i][i] = 1
    while k > 0:
        if k & 1:
            res = matrix_multiply(res, A)
        A = matrix_multiply(A, A)
        k >>= 1
    return res

# 邻接矩阵
A = [[0, 1, 1, 1, 0, 0, 0, 0],
    [1, 0, 1, 1, 0, 0, 0, 0],
    [1, 1, 0, 0, 1, 0, 1, 0],
    [0, 0, 1, 0, 0, 1, 0, 1],
    [1, 1, 0, 1, 0, 0, 1, 0],
    [0, 0, 1, 0, 1, 0, 0, 1],
    [0, 1, 0, 1, 1, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 0, 0]]

# 初始向量
B = [[1 if (i >> j) & 1 == 0 and (i >> (j-1) & 1) != 1  else 0 for j in range(n) ]for i in range(1 << n)]

# 矩阵快速幂
C = matrix_multiply(B, matrix_power(A, m))

# 计算答案
ans = sum(C[i][j] for i in range(1 << n) for j in range(n))

return ans