状态压缩dp 状压dp 详解
说到状压dp,一般和二进制少不了关系(还常和博弈论结合起来考,这个坑我挖了还没填qwq),二进制是个好东西啊,所以二进制的各种运算是前置知识,不了解的话走下面链接进百度百科
https://baike.baidu.com/item/%E9%80%BB%E8%BE%91%E8%BF%90%E7%AE%97/7224729?fr=aladdin
现在我就当你明白了所有前置知识点了
状压dp就是通过一系列操作(例如用二进制)复杂的状态进行压缩,然后转移
现在我们来一道板子题感受一下状压dp
https://www.luogu.org/problemnew/show/P1879
看这个题很明显就可以用二进制状压,1表示种,0表示不种
但是我们要进行状态的合法判断
根据这个题的题目,我们发现,相邻位置上有两个1是不合法的,在不能种草的地上种草是不合法的,那么如何解决呢,给点时间自己想一想吧
相邻位上有两个1,那么我们可以把原数左移一位再和自己去&,如果结果大于0,就说明存在相邻位上有两个1,不合法,若等于0即为合法
证明也很简单,举几个例子就能理解了
到这里左右判断就搞定了,还有上下呢?
明白了左右,上下就更简单了,上一行和当前行也取&,同样大于0不合法,因为如果有一位上为1,那么就说明这两行在同一列上都有1,是不合法的
最后是和原图的01判断,也很简单,只需要和原图取&,若结果等于当前行的状态即为合法,反之不合法
证明:若结果不为当前状态则说明,在某一位置,当前状态为1,原图为0,所以不成立
好啦,问题都解决了,我们上代码吧
#include<iostream> #include<cstdio> using namespace std; int n,m; long long ans; int land[20][20],la[20],able[(1<<12)+3];//数组注意大小 long long f[20][(1<<12)+3]; int main() { scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { scanf("%d",&land[i][j]); la[i]=(la[i]<<1)+land[i][j]; } } for(int i=0;i<(1<<m);i++) { if(!(i&(i<<1))) { able[i]=1; } } f[0][0]=1; for(int i=1;i<=n;i++)//第i行 { for(int j=0;j<(1<<m);j++)//状态为j { if(able[j]&&((j&la[i])==j)) { for(int k=0;k<(1<<m);k++)//枚举上一行状态 { if(!(j&k)) { f[i][j]+=f[i-1][k]; f[i][j]%=100000000; } } } } } for(int i=0;i<(1<<m);i++) { ans+=f[n][i]; ans%=100000000; } printf("%lld",ans); }
感觉如何??再来道题练练手
https://www.luogu.org/problemnew/show/P2704