状压dp(总结)状态压缩
状压这个和二进制分不开关系
所以,对于二进制的熟悉是必不可少的技能
& 与操作,1不变,0变0
| 或操作,0不变,1变1
^ 异或操作,0不变,1取反
~ 取反操作,把每一个二进制位0变1,1变0
还有一些复杂操作可以根据这些去理解
状态压缩
所谓状态压缩就是把dp的每一次转移时的状态用二进制来表示
或者用二进制来间接表示
比如
这里有10个苹果,编号1-10
我拿走了1,4,7,9这四个苹果
那么我们可以用011011010这一串二进制数来表示现在的状态
0表示这个位置没有苹果,1表示有
那么这就是一个状态
相比拿一个bool型的数组,这样表示更方便,内存更小,操作更简单
现在我想把拿走的苹果放回去,没拿走的拿走
那么状态就变成100100101
直接取反 a=011011010 b=100100101
a==~b;
这时候就充分展示了状态压缩的快捷性
下面我们讲一道例题。。。。。
在n*n(n≤20)的方格中放置n个车,每个车可以攻击所在的行和列,求方案总数
直接上排列组合,n!,很好理解啊
在n*n(n≤20)的方格棋盘上放置n 个车,某些格子不能放,求使它们不能互相攻击的方案总数。
这时候一些格子不能放,就要考虑每一行的情况
但是 ,,,,即使每一行中有的格子不能放,最终还是每一行每一列都要有一个车子
所以我们用s这个int型的数来表示现在的状态(行状态)
如果这一列现在有车子,那这一位就是1
所以最终s一定会变成11111111111(全是1)只有这样才能把n个车全部放进去
这样这一状态有几个车子说明这就是第几行
那这样转移方程就有了
dp[s]+=dp[s^(s&-s)];
for(;j>0;j-=(j&-j)){ dp[i]+=dp[i^(j&(-j))]; }
还有个问题,有些格子不能放车
这怎么办???????????
还记得前面的苹果吗
用1表示这里能放车子,0表示不可以
在状态转移的时候&一下就可以啦
j=i&s[num];
代码如下
#include<bits/stdc++.h> #define ll long long using namespace std; int n,m; long long dp[1<<20]; int s[25]; int main(){ memset(s,0x7fffffff,sizeof(s)); //if(s[1][1]==1)cout<<"sh"; //cout<<(s[1][1]&0)<<endl; scanf("%d%d",&n,&m); int x,y; for(int i=1;i<=m;i++){ scanf("%d%d",&x,&y); s[x]-=(1<<(y-1)); } dp[0]=1; for(int i=1;i<(1<<n);i++){ int j,num=0; for(j=i;j>0;j-=(j&(-j))){ num++; } j=i&s[num]; for(;j>0;j-=(j&-j)){ dp[i]+=dp[i^(j&(-j))]; } } printf("%lld",dp[(1<<n)-1]); }
这样基本的状压dp就结束了
也许你已经发现了
所有的n都小于等于20
因为int只有32位
这也是状压的前提
所以当你一直为空间时间复杂度着急时
就去考虑状压
而状压前先找到可以状压的数
就是32位以内的
over