状态压缩动态规划
原理讲解
状态压缩DP其实就是把一种状态通过二进制的形式储存下来,从而利于进行状态的转移。
例如5个盒子排成一排,其中第1,3,4个盒子有糖果,那么可以表示为
这类问题通常有一定的模板,在以下情况可能要用到状压DP:
- 所输入的内容只有两种状态,就比如只有
和 。 - 统计合法状态的方案数 or 最大值。
不能用其他方法。
解决这类问题的状态一般都设置为如
那么,状压DP有什么缺点呢?其实也很容易发现:即使是长整型 long long 也只有64位,也就是说最多只能存64个状态,这真是一个致命缺点(什么,你用bitset
??)。
例题解析
理论会了,就开始实践吧!
来看这题 Corn Fields G,这题可以说是超超超经典的一道状压DP题。首先,也是这题的关键,就是将土地状态转化为十进制储存,如将111
转化为7
。要计算方案总数,那一要先判断那种状态是合法的,判断一种状态是否合法要考虑三个问题,行内合法以,行间兼容,和是否在肥沃的土壤上,也就是这一步体现了题目所说的"位运算的妙用"。
行内兼容
举个栗子:在 &
运算符的运算规则:相同为1否则为零,再往深了想一下是不是只要 i&(i>>1)=0
,就表明状态 i
是合法的?就比如 1010&0101=0。是不是豁然开朗了。 i&(i<<1)=0
也是可以的。
行间兼容
有了前面的经验的经验这个就简单了,只要 i$j=0
第 1010&0100=0
这两行就可以兼容。
是否在肥沃的土壤上
这个也很好理解,完成这一步骤还是要用到 &
运算。
我们来找一下规律:
你发现了什么?没错对于土地状态 i
和当前状态j
只要 i&j==j
,那么状态j
就是OK的。
这样就可以愉快的设计算法了:
- 定义
为第i
行第j
种状态的方案数。 - 初始状态
,即第1行的每种状态都是一种方案 - 状态转移方程
- 最终答案
参考代码
#include<bits/stdc++.h> using namespace std; const int mod=100000000; int num[13],dp[13][1<<13],ans=0; int g[1<<13]; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ int tmp; cin>>tmp; num[i]=(num[i]<<1)+tmp; } } for(int i=0;i<(1<<m);i++){ if(!(i&(i>>1))&&!(i&(i<<1))){ g[i]=1; if((i&num[1])==i) dp[1][i]=1; } } for(int x=2;x<=n;x++){ for(int j=0;j<(1<<m);j++){ if(((j&num[x-1])==j)&&g[j]==1){ for(int y=0;y<(1<<m);y++){ if(((y&num[x])==y)&&!(j&y)&&g[y]){ dp[x][y]=(dp[x][y]+dp[x-1][j])%mod; } } } } } for(int i=0;i<(1<<m);i++) ans=(ans+dp[n][i])%mod; printf("%d\n",ans); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)