状压DP
[SCOI2005]互不侵犯
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。
国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
输入格式:
只有一行,包含两个数n,m ( 1 <=n <=9, 0 <= m<= N * N)
输出格式:
所得的方案数
初学者很天真地想写爆搜,但总感觉不妥,然后就听说这是状压DP……
这怎么DP呀,要记录上一行是什么样吗?如何记录这么多状态?
这就是状态压缩要解决的问题。
对于每一行我们考虑用bool数组记录每一格的情况,1表示有,0表示没有,例如:10010101,表示第一,四,六,八格有国王。
那么我们可以发现这个东西可以看做一个8位二进制数149,别告诉我149存不下。
所以我们就可以将一行的状态压缩成一个二进制数,而处理这些状态就显然需要用位运算了。
关于基础的位运算我们就不在赘述,这里介绍几个状压DP中常用的操作。
S&(1<<i):判断第i位的情况,是0还是1.拿这道题来说就是状态S的第i格是不是有了国王。(需要注意的是i是从右往左数的)
S|(1<<i):将S的第i位设置为1,往i这一位放上国王。
S|~(1<<i):将S的第i位设置为0,往i这一位拿下国王(虽然这里不用)
S1&S2:判断S1和S2是否有交集(对于某一位或几位i,有S1[i]==S2[i]==1),这里可以判断上下两行的国王会不会冲突。
S1&(S2<<1):将S2左移一位再进行按位与,此时S1和S2是错位的,就可以用来判断右上和左下的冲突。
S1&(S2>>1):同上,判断左上和右下的冲突。
注:弄不清运算符优先级的同学们一定要多加括号。
科普完我们就该讲一下这道题的思路了(^_^),我们令f[i][j][k],表示第i行,状态为j且前i行已放置k个国王的方案数。
那么显然f[i][j][k]=∑f[i-1][j′][k′]
其中k=k'+状态j的国王数。
最终ans=Σf[n][i][m]
我们可以预处理出所有状态以及该状态的国王数。
代码如下:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n,m; long long f[10][150][150],ans; int num[150],s[150],total; int main() { scanf("%d%d",&n,&m); for(int i=0;i<(1<<n);i++) { if(i&(i<<1))continue;//检查当前状态是否冲突 int k=0; for(int j=0;j<n;j++) if(i&(1<<j))//数一数这个状态需要多少国王 k++; s[++total]=i;//记录状态 num[total]=k;//国王个数 } f[0][1][0]=1; for(int i=1;i<=n;i++) for(int j=1;j<=total;j++) for(int k=0;k<=m;k++) if(k>=num[j]) for(int t=1;t<=total;t++)//第i-1行可能是什么 if(!(s[t]&s[j])&&!(s[t]&(s[j]<<1))&&!(s[t]&(s[j]>>1)))//上下无冲突。 f[i][j][k]+=f[i-1][t][k-num[j]]; for(int i=1;i<=total;i++) ans+=f[n][i][m]; cout<<ans; }
请原谅本蒟蒻现学现卖QWQ
然后我又找来了另一道题:
[USACO06NOV]玉米田Corn Fields
貌似也是一道裸题,那不妨我们就用这道题来总结套路:
令f[i][j],表示第i行j种状态的方案数。
首先预处理每一行可能的状态,我们要保证左右不重复,且不能种在贫瘠的土地上。
对于每一行i的每一种状态j,考虑前一行的每种状态k,若不冲突,则令f[i][j]+=f[i-1][k].
最终统计所有f[n],(习惯上令n表示行数,m表示列数)
这就是刚学习状压的蒟蒻的感受:
先预处理状态,对于每种状态考虑是否与前一状态重复并递推答案。最后统计终点的所有状态的答案之和。
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<cstdio> 5 using namespace std; 6 const int mod=100000000; 7 int n,m,f[20][1<<20]; 8 struct cym{ 9 int s[1<<20],num; 10 }a[15]; 11 void find(int S,int now) 12 { 13 int total=0; 14 for(int i=0;i<(1<<m);i++) 15 if(!(i&(i<<1))&&!(i&(i>>1))&&!(i&S))//我们可以用左右移与自身匹配来判断左右冲突。 16 a[now].s[++total]=i;//now来表示土地情况,状态不与now冲突。 17 a[now].num=total; 18 } 19 int main() 20 { 21 scanf("%d%d",&n,&m); 22 for(int i=1;i<=n;i++) 23 { 24 int now=0; 25 for(int j=1;j<=m;j++) 26 { 27 int x; 28 scanf("%d",&x); 29 now=(now<<1|1)-x; 30 } 31 find(now,i); 32 } 33 for(int i=1;i<=a[1].num;i++) 34 f[1][i]=1; 35 for(int i=2;i<=n;i++) 36 for(int j=1;j<=a[i].num;j++) 37 for(int k=1;k<=a[i-1].num;k++) 38 if(!(a[i].s[j]&a[i-1].s[k]))//与前一状态是否冲突 39 { 40 f[i][j]+=f[i-1][k]; 41 f[i][j]%=mod; 42 } 43 int ans=0; 44 for(int i=1;i<=a[n].num;i++) 45 ans=(ans+f[n][i])%mod; 46 printf("%d",ans); 47 }