状态压缩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];
}
posted @ 2023-08-10 22:51  lwr2010  阅读(6)  评论(0编辑  收藏  举报