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); }