状态压缩DP入门---农夫种田

文章目录

  • 一、行内左右不相邻种植的所有状态
  • 二、上下行 之间 不相邻的状态
  • 三、原数组转换为二进制并取反
  • 四、初始化第一行
  • 五、重点: 逐行排查
  • 六、全部代码

参考博客:
这一篇思路写的很清晰:
https://www.cnblogs.com/BlackStorm/p/4706243.html

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

思路:
状态压缩DP入门---农夫种田_第1张图片
状态压缩可以以行为基准,或者以列为基准,本文以行为基准进行讨论。
方块是 M * N
所以每一行有 N 个方块,每个方块有两种选择:种 或者 不种
那么每一行所有的种植状态 数量就是: 2 ∗ 2 ∗ 2...... ∗ 2 = 2 N 2 * 2 * 2 ......* 2 =2^N 222......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种状态时,可行的总方案数
那么条件是:

  • 遍历第二行到最后一行;
  • 对于当前行的土壤条件,遍历所有 种植状态,选择和 土壤条件符合的 种植状态 s ,再遍历每一种种植状态k,当满足 种植状态s 和 上一行的 土壤状态 匹配 并且 s 和 k 满足上下行 不相邻种植 ,那么就加上上一行种植状态为 k 时的方案数
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)    

你可能感兴趣的:(数据结构与算法)