1087: [SCOI2005]互不侵犯King
Time Limit: 10 Sec
Memory Limit: 162 MB
Submit: 3014
Solved: 1767
Description
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上
左下右上右下八个方向上附近的各一个格子,共8个格子。
Input
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
Output
方案数。
Sample Input
3 2
Sample Output
16
题解:
作为一个第一次写状压DP的蒟蒻,直接学习hzwer学长的代码,有很多很巧妙的状压DP技巧,值得学习(我就是给他打标记的而已)
很显然状态是f[i][j][
status],表示的是前i行,用了j个国王时,第i行的状态
对于状态,其中不可能有相邻的两个1,比如1101,1011都是不合法的状态,所以我们将所有合法的状态预处理来
其次,因为每个状态都是从上一行转移过来的,所以我们也需要判断任意两个状态之间会不会冲突(因为左上,右上,正上,这三个方向是不能同时有子的),这样剪一剪枝之后时间复杂度就很理想了
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
int n,m,S,cnt[1<<9];
ll f[10][100][1<<9];
bool c1[1<<9],c2[1<<9][1<<9];
ll ans;
void pre()
{
int s;
for(int i=0;i<=S;i++)
if((i&(i>>1))==0)//判断有没有相邻的方式
{
s=0;
for(int x=i;x;x>>=1)s+=(x&1);
cnt[i]=s;//求每个状态1的个数
c1[i]=1;//为合法的排列
}
for(int i=0;i<=S;i++)
if(c1[i])
for(int j=0;j<=S;j++)
if(c1[j])
if(((i&j)==0)&&((i&(j>>1))==0)&&((j&(i>>1))==0))
c2[i][j]=1;//i,j为两种合法的可以在
}
int main()
{
scanf("%d%d",&n,&m);
S=(1<<n)-1;
pre();
int tot=0;
for(int i=0;i<=S;i++)
if(c1[i]) f[1][cnt[i]][i]=1;
for(int j=1;j<n;j++)
for(int k=0;k<=S;k++)//k是在枚举上一行的状态
if(c1[k])
for(int i=0;i<=S;i++)//i是在枚举这一行的状态
if(c1[i])
if(c2[k][i])
for(int p=cnt[k];p+cnt[i]<=m;p++)
f[j+1][p+cnt[i]][i]+=f[j][p][k];
ll ans=0;
for(int i=0;i<=S;i++) ans+=f[n][m][i];
printf("%lld",ans);
}