https://blog.nowcoder.net/n/c5a2d2d13b4a4decbdfcdb883bc0fc3a
https://blog.nowcoder.net/n/481f5ddf1011462aa52b4b1c38a55acb
下面这个代码有注释,很清晰:
https://www.pianshen.com/article/44052961/;jsessionid=B891D5FA2357491435F5F3220599CF7C
我写的代码主要参考这个:
https://blog.csdn.net/harrypoirot/article/details/23163485
思路:
状态压缩可以以行为基准,或者以列为基准,本文以行为基准进行讨论。
方块是 M * N
所以每一行有 N 个方块,每个方块有两种选择:种 或者 不种
那么每一行所有的种植状态 数量就是: 2 ∗ 2 ∗ 2...... ∗ 2 = 2 N 2 * 2 * 2 ......* 2 =2^N 2∗2∗2......∗2=2N = 1 << N (位运算)
接下来重点来了!!!
以三列为例,一共的状态数是 2 3 = 8 2 ^ 3 = 8 23=8
所有的状态罗列出来是:
状态(二进制) | 对应的十进制数 |
---|---|
000 | 0 |
001 | 1 |
010 | 2 |
011 | 3 |
100 | 4 |
101 | 5 |
110 | 6 |
111 | 7 |
由于 0 表示不种,1表示种,题目说 不能挨着种植,那么同一行内,种植的块不能挨着种;并且 这一行种植的块 和 上下行种植的块 也不能挨着
先来看 行内:左右相邻的格子不能种植,那么也就是 左移一位后 和 自身相位与 为0,如果存在相邻种植的格子,则一定会保留位1,不可能得出0的结果,据此可以枚举出所有满足行内间隔种植的状态,再进行后续判断。
以下代码是 列举出 每一行 满足行内 不相邻种植的 所有状态数:
def find_allstate():
most = 1 << N # 所有状态
for i in range(most):
if i & (i<<1) == 0:
states.append(i)
sum_states += 1 # sum_states为满足行内间隔种植的状态数
这一行的某个位置种植了,下一行的同一位置就不能种植,可以知道 两行的种植状态相位与要为0
states[k] & states[s] == 0
然而并不是每个有效的行种植状态对于每一行都有效,因为有的行存在一些位置是不能种植的,用0表示。为了方便判断和计算,我们考虑将行状态取反,即0表示肥沃,1表示不肥沃,这样只有当行种植状态和行状态相位与为0,这个种植状态才在该行有效,因为如果种在了不肥沃的格子上,相位与会保留位1,结果不为0。
将原数组的每行转换为二进制数 这个花费我的时间最多:
有三种写法:
cur[j]
为当前行列索引 为 j 的元素
写法一:
reverse_arrs = [0 for _ in range(M)]
for j in range(N):
if cur[j] == 0:
reverse_arrs[i] = reverse_arrs[i] | (1 << (N-j-1)) # 这里因为j是从0开始 所以要再减去1
写法二:
reverse_arrs = [0 for _ in range(M)]
for j in range(N):
if cur[j] == 0:
reverse_arrs[i] = reverse_arrs[i] + (1 << (N-j)) # j 是从 1 开始 的话 就(N-j)
写法三:
下面的第三种方法由于python语言的特性,并不能得出我们想要的结果,所以下面的最好不用 当然 其他语言可以正常使用 ~
取反符号
reverse_arrs = [0 for _ in range(M)]
for j in range(N):
reverse_arrs[i] = (reverse_arrs[i] << 1) | ~(cur[j]) # 将原数组每一行的子数组转换成 二进制数 并取反
因为第一行只需要考虑 本行 相邻不能种 的情形,而且第一行上面没有别的行,需要初始化
将 行内左右不相邻种植的所有状态 依次和 二进制数组的第一行进行 相位与,找出 既满足题目给定的土壤条件,又满足 行内左右不相邻种植的条件,那么计数 1
# 初始化dp数组的第一行
for i in range(sum_states):
if states[i] & reverse_arrs[0] == 0:
dp[0][i] = 1
设 dp[i] [state[j] ] 表示 前i行,当第i行采取第j种状态时,可行的总方案数
那么条件是:
for i in range(1,M):
for s in range(sum_states):
if states[s] & reverse_arrs[i] == 0:
for k in range(sum_states):
if dp[i-1][k] != 0 and states[k] & states[s] == 0: # dp[i-1][k] != 0 就表明 上一行 采用了 状态K ;此行代码和下行代码 作用相同
# if states[k] & reverse_arrs[i-1] == 0 and states[k] & states[s] == 0:
dp[i][s] = (dp[i][s] + dp[i-1][k]) % mod
M,N = [int(item) for item in input().split()]
reverse_arrs = [0 for _ in range(M)]
states = []
sum_states = 0
mod = 100000000
for i in range(M):
cur = [int(item) for item in input().split()]
for j in range(N):
if cur[j] == 0:
reverse_arrs[i] = reverse_arrs[i] | (1 << (N-j-1)) # 这里因为j是从0开始 所以要再减去1
# print('cur[j]',~cur[j])
# reverse_arrs[i] = (reverse_arrs[i] << 1) | ~(cur[j]) # 将原数组每一行的子数组转换成 二进制数 并取反
#其他写法为:
# if cur[j] == 0:reverse_arrs[i] = reverse_arrs[i] + (1 << (N-j)) # j 是从 1 开始 的话 就(N-j)
print('reverse_arrs',reverse_arrs)
# def find_allstate():
# most = 1 << N
# for i in range(most):
# if i & (i<<1) == 0:
# states.append(i)
# sum_states += 1
# find_allstate()
most = 1 << N
for i in range(most):
if i & (i<<1) == 0:
states.append(i)
sum_states += 1
print('len(states), sum_states',len(states),sum_states)
dp = [[0 for _ in range(sum_states) ] for _ in range(M)]
# dp[i][j]表示 前i行,第i行种植状态为states[j]时的方案数
# 初始化dp数组的第一行
for i in range(sum_states):
if states[i] & reverse_arrs[0] == 0:
dp[0][i] = 1
for i in range(1,M):
for s in range(sum_states):
if states[s] & reverse_arrs[i] == 0:
for k in range(sum_states):
if dp[i-1][k] != 0 and states[k] & states[s] == 0:
# if states[k] & reverse_arrs[i-1] == 0 and states[k] & states[s] == 0:
dp[i][s] = (dp[i][s] + dp[i-1][k]) % mod
ans = 0
for i in range(sum_states):
ans = (ans + dp[M-1][i]) % mod
print(dp)
print(ans)