BZOJ1087: [SCOI2005]互不侵犯King
Description
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。
国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
Input
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
Output
方案数。
Sample Input
3 2
Sample Output
16
题解Here!
状压DP的题一般数据范围都很小,比如这题。
设 f [ i ][ j ][ s ] 就表示在只考虑前i行时,在前 i 行(包括第 i 行)有且仅有 s 个国王,且第 i 行国王的情况是编号为 j 的状态时情况的总数。
gs[ x ] 表示此状态下这一行放的国王个数。
于是状态转移方程长这样:f [ i ][ j ][ s ]=∑f [ i-1 ][ k ][ s-gs[ j ] ]
而 k 就代表第 i-1 行的国王情况的状态编号。
那怎么判断某国王周围八个方向是否有国王呢?
在同一行国王之间的关系我们可以直接在预处理状态时舍去那些不符合题意的状态,那其他的情况呢?
位运算!
sit[j] & sit[k] (及上下有重复的king)
(sit[j]<<1) & sit[k] (及左上右下有重复king)
sit[j] & (sit[k]<<1) (及右上左下有重复king)
于是就可以处理掉那些不符合题意的状态了。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; int n,m,S=0,a[(1<<10)+1],b[(1<<10)+1]; long long ans=0,dp[10][(1<<10)+1][100]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } void dfs(int x,int s,int sum){ if(x>=n){ a[++S]=s; b[S]=sum; return; } dfs(x+1,s,sum); dfs(x+2,s+(1<<x),sum+1); } int main(){ n=read();m=read(); dfs(0,0,0); for(int s=1;s<=S;s++)dp[1][s][b[s]]=1; for(int i=2;i<=n;i++) for(int j=1;j<=S;j++) for(int k=1;k<=S;k++){ if((a[j]&a[k])||((a[j]<<1)&a[k])||(a[j]&(a[k]<<1)))continue; for(int s=m;s>=b[j];s--)dp[i][j][s]+=dp[i-1][k][s-b[j]]; } for(int s=1;s<=S;s++)ans+=dp[n][s][m]; printf("%lld\n",ans); return 0; }