状压DP
状态压缩
本节需要比较强的位运算基础素养
有些动态规划需要的状态很多,比如这周第一节我们讲过那个选位置,不能相邻的动态规划问题,因为那个题目只和最后一个位置有关,所以直接设
但是如果你需要记录多个位置的状态,那就要变成
这样很明显不好,而且转移的时候会比较麻烦。
我们可以简化一下,如果有
比如如果这里存了一个数字
如果你有
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
( 1 <=N <=9, 0 <= K <= N * N)
设
我们设
1、状态
2、设状态
枚举我们从哪个状态转移过来,假如是
那么又要求,
判断两个二进制数
第一个可以判断没有同一个位置上有
第二个和第三个可以判断没有相邻位置上都是
代码总结:
cpp
int n,k;
cin>>n>>k;
int S=(1<<n);//状态上界
for(int s=0;s<S;++s)
{
int pre=0;
if(s&(s<<1))
{
cnt[s]=-10000;//如果s有相邻两位是1,那么这个状态不合法
continue;
}
for(int k=0;k<n;++k)
{
if((s>>k)&1)//如果s的第k位上数字是1
{
++cnt[s];//预处理每个合法状态里面1的个数
}
}
dp[1][s][cnt[s]]=1;//直接处理出第一排的状态。
}
for(int i=2;i<=n;++i)//开始北伐,呃不,转移
{
for(int s=0;s<S;++s)
{
if(cnt[s]<0) continue;//不合法状态
for(int pre=0;pre<S;++pre)
{
if(s&pre) continue;//有并列的
if(s&(pre<<1)) continue;
if(s&(pre>>1)) continue;//这两句意思是,相邻的两排有之差一列的。
for(int j=cnt[s]+cnt[pre];j<=k;++j)//下界可以直接设cnt[s]+cnt[pre],因为光s和pre里面就有这么多
{
dp[i][s][j]+=dp[i-1][pre][j-cnt[s]];
}
}
}
}
int sum=0;
for(int s=0;s<S;++s) sum+=dp[n][s][k];//所有可能的方案都加起来
cout<<sum<<'\n';//爱你
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
嘿嘿