矩阵的每个位置只有两种可能的状态,加上 N, M 的范围这么小,很容易想到要用到二进制,往这个思路想下去就想到了状压 DP。
大致思路如下:
- 输入 N, M,矩阵每行的状态就有 numState = 2M 个。
- 假设 1 代表题中的 ‘X’,0 代表题中的 ‘O’。可以先从 [0, numState - 1] 里面挑出符合条件的数存进一个数组 stateAllowed,所谓“符合条件”就是这些数的二进制表示没有连续 3 个 1,例如 8 = (1000)2 是符合条件的,而 7 = (0111)2 和 15 = (1111)2 是不符合条件的。
- 设置 dp[N][numState][numState],dp[i][j][k] 表示矩阵第 i 行的状态为 j,前一行的状态为 k 时,符合条件的矩阵有多少个。状态转移方程如 Pseudocode1 所示。dp 的初始化稍微有些难想到。我们假设矩阵的行号从 0 开始计数,采用 Pseudocode2 所示的方法对 dp 进行初始化。
""" Pseudocode1 """
for j in stateAllowed:
for k in stateAllowed:
for p in stateAllowed:
if j & k & p == 0: // 这个判断能保证矩阵在列上不会出现连续的 3 个 1
dp[i][j][k] += dp[i - 1][k][p]
""" Pseudocode2 """
for j in stateAllowed:
dp[0][j][0] = 1 // dp[0 - 1]并不存在, 也就是矩阵第 0 - 1 行并不存在
// 但我们可以假想它存在并且只有状态 0, 以此来对第 0 行进行初始化
时间复杂度为 O(N * 23M)
Python 代码如下:
import sys
N, M = list(map(int, sys.stdin.readline().strip().split()))
numState = 2 ** M
stateAllowed = []
for i in range(numState):
cnt, flag = 0, False
temp = i
while temp:
if temp & 1:
cnt += 1
else:
cnt = 0
if cnt >= 3:
flag = True
break
temp >>= 1
if not flag:
stateAllowed.append(i)
dp = [[[0 for _ in range(numState)] for __ in range(numState)] for ___ in range(N)]
for i in stateAllowed:
dp[0][i][0] = 1
for i in range(1, N):
for j in stateAllowed:
for k in stateAllowed:
for p in stateAllowed:
if j & k & p == 0:
dp[i][j][k] += dp[i - 1][k][p]
ans = 0
for i in stateAllowed:
ans += sum(dp[N - 1][i])
print(ans)
还有一种比较简单的方法,也就是 DFS + 二进制枚举,如果在比赛时想不出状压 DP 就可以用这个方法。具体思路就不写了,挺容易的。不过不推荐 Python 选手用这种方法,因为这种方法时间复杂度为 O(2N*M),在本题不放松时间限制的情况下可能会超时,因为 Python 运行效率比较低。这种方法推荐 C++ 选手使用。