[状压dp] P1896-SCOI2005-互不侵犯(小国王)
题意描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
数据范围
1 <=N <=9, 0 <= K <= N * N
题目分析
类似蒙德里安的梦想的经典状态压缩dp。
1.状态表示
三维f[n][k][s],表示当前行为n、已放置了k个国王、且当前行放置国王的状态为s的方案数。
目标:
2.合法判断
由于要求国王互不攻击,故当前状态s必定有合法与不合法之分,且当前行的状态是否合法,仅与当前行的状态和上一行的状态有关。
设当前行的状态为s,上一行的状态为s'。
- 对于当前行
s的二进制表示中不能有两个连续的1。 - 对于当前行和上一行
1.显然,s与s'在任意相同位置的值不能均为1。即s&s'=0
2.根据题意,s与s'在任意相邻的位置不能均为1。即,s|s'的二进制表示不能有两个连续的1。
3.状态转移方程
-
-
当前行为
i、已放置了j个国王、且当前行放置国王的状态为k的所有方案数等于当前方案数+第i-1行已放置j-cnt[k]个国王,且状态为m的方案数。 -
其中,
cnt[k]为状态k中1的个数,m为满足k&m==0且k|s的二进制表示没有两个连续的1的所有状态。
4.代码处理
- 可以预处理出
到
中所有合法(即当前行无两个连续的1)的状态值
j,且记录下对应状态值的二进制表示中1的个数cnt[j]。
5.代码实现
#include<bits/stdc++.h>
using namespace std;
const int N=11,K=110,S=1<<11;
long long f[N][K][S],cnt[S];
bool st[S]; //判断预处理出的状态值是否合法
int n,k;
main()
{
cin>>n>>k;
for(int i=0;i<1<<n;i++) //预处理0到2^n-1的所有状态数
{
bool flag=1;
int c=0;//记录i中1的个数
for(int j=0;j<n;j++)
{
if(((i>>j)&1)&&(i>>(j+1)&1)) //若有两个相邻的1则非法
{
flag=0;break;
}
if((i>>j)&1) c++;
}
st[i]=flag?1:0;
if(st[i]) cnt[i]=c;
}
for(int j=0;j<1<<n;j++)
if(st[j]) f[1][cnt[j]][j]=1;//处理第一行的所有合法方案
for(int i=2;i<=n;i++) //从第二行开始处理
for(int j=0;j<1<<n;j++)
for(int m=0;m<1<<n;m++) //枚举当前行的所有状态j与上一行的所有状态m
if((j&m)==0&&st[j|m]) //如果j与m合法
for(int t=cnt[j];t<=k;t++)
f[i][t][j]+=f[i-1][t-cnt[j]][m];
long long ans=0;
for(int j=0;j<1<<n;j++) ans+=f[n][k][j]; //对所有合法方案求和
cout<<ans;
第一篇题解,自学了markdown,从latex在线编辑器里拖了一堆公式引用进来,感觉不错。

浙公网安备 33010602011771号