BZOJ1087:[SCOI2005]互不侵犯——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=1087
Description
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上
左下右上右下八个方向上附近的各一个格子,共8个格子。
Input
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
Output
方案数。
Sample Input
3 2
Sample Output
16
——————————————————————————————
n很小,暴力太麻烦,考虑状压。
设f[i][j][k]表示前i行放j个国王且第i行排成k情况的时候的情况数有多少。
g[i]表示一行国王排成i情况的时候有几个国王。
转移的时候显然是f[i][j][k]+=f[i-1][j-g[k]][l]
其中保证l合法,并且j最小值为g[k]+g[l]。
于是得到算法构架:
枚举i,枚举k,判断k的合法性,枚举l,判断l的合法性,枚举j,计算。
Q1:g怎么算?
A1:求g[i],我们可以通过将i右移,然后判断i最后一位为0还为1,所以答案为:g[i]=g[i>>1]+(i&1);
Q2:如何判断状态合法?
A2:我们判断相邻行i和j状态之间是否合法,首先判断i和j本身是否合法——通过将本身左移,再和原状态&一下,如果不为0就一定撞上了。
再考虑i和j,同样的思路,将j左/右移和i&(当然反过来也可以),如果不为0就一定撞上了。
#include<cstdio> #include<iostream> #include<cmath> #include<cstring> #include<algorithm> #include<queue> using namespace std; typedef long long ll; const int N=512; const int INF=2147483647; inline int read(){ int X=0,w=0;char ch=0; while(!isdigit(ch)){w|=ch=='-';ch=getchar();} while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar(); return w?-X:X; } int g[N]; ll ans,f[10][100][N]; int main(){ int n,m; scanf("%d%d",&n,&m); if(m>25||m>=n*n)puts("0"); else{ int t=1<<n;f[0][0][0]=1; for(int i=1;i<t;i++)g[i]=g[i>>1]+(i&1); for(int i=1;i<=n;i++){ for(int j=0;j<t;j++){ if(g[j]<=m&&!(j&j>>1)){ for(int k=0;k<t;k++){ if(g[k]<=m&&!(k&k>>1)&&!(k&j)&&!(j&k>>1)&&!(j&k<<1)){ for(int l=g[j]+g[k];l<=m;l++){ f[i][l][j]+=f[i-1][l-g[j]][k]; } } } } } } for(int i=0;i<t;i++)ans+=f[n][m][i]; printf("%lld\n",ans); } return 0; }