状态压缩dp
状态压缩dp
1.用二进制表示状态,用十进制数储存状态
2.用位运算筛选出合法状态
3.用位运算判断状态转移条件
4.计算时每个类类加上一行的兼容类
例题 小王国
题目描述:在\(n*n\)的棋盘上放\(k\)个国王,国王可以攻击相邻的8个格子,求他们无法互相统计的方案总数
输入:\(n,k(1<=n<=10,0<=k<=n^2)\)
输出:方案总数
例题分析
用二进制数表示状态,用十进制数字来储存 1表示这里有个国王,0表示这里没有国王
当\(n=3\)时每行的所有状态:\(000,001,010,011,100,101,110,111\),其中,两个1不能相邻,所以排除\(011,110,111\)这三个数。
用位运算来判断每行是否合法。
判断行内是否合法
- 如果
!(i&i>>1)
为真,则i合法。
- 举个例子:当\(i=5\)时,它的二进制数就是101,右移后就是010,他们的1是错开的。
判断上下两行是否合法
- 如果
!(a&b) && !(a&b>>1) && !(a&b<<1)
为真,则i合法。
-a&b>>1
是判断左对角线是否有连着的国王
我们可以设置状态\(f[i,j,a]\)表示前i行已经放了j个国王,第i行的第a个状态的方案数
状态计算:\(f[i,j,a]=\sum f[i-1,j-c[a],b]\) c[a]表示第i行已经放了a状态的时候,已经有几个国王 b表示i-1行的方案 最终答案:\(ans=\sum f[n,k,a]\)
例题代码解读
//预处理
for(int i=0; i<(1<<n); i++){//枚举一行的所有状态(在二进制下,每位只有0和1两种状态,所以方案总数用可重复求幂法,2的n次方)
if(!(i&i>>1){//判断这行是否合法
s[cnt++]=i;//储存合法的状态
for(int j=0; j<n; j++){
num[i]+=(i>>j&1);//统计每个合法状态包含的国王数量(含有1的个数)
}
}
}
接着进行状态计算
f[0][0][0]=1//不放国王也是一种方案
for(int i=1; i<=n+1; i++){
for(int j=0; j<=k; j++){
for(int a=0; a<cnt; a++){
for(int b=0; b<cnt; b++){
int c=num[s[a]];
if((j>=c) && !(s[b]&s[a]) && !(s[b]&(s[a]<<1)) && !(s[b]&(s[a]>>1))){
f[i][j][a]+=f[i-1][j-c][b]
}
}
}
}
}
最后对合法状态进行累加求和
for(int i=0; i<cnt; i++){
ans+=f[n][k][i];
}