Solution [SCOI2005]互不侵犯
题目大意:给定两个数(n),(k),求在(n^2)大小的棋盘内放置(k)个国王的方案数
状压(dp)
分析一下题目:(n)很小啊,(1 leq n leq 9),而且国王的攻击范围也很有特点,蒟蒻马上就想到了状压(dp)
稍加分析即可列出(dp)的状态,以(f[i][s][k])来表示前(i)行,第(i)行的状态为(s),到第(i)行共计有(k)个国王的方案总数
状态容易想,转移方程呢?
(f[i][s][k] = sum f[i - 1][z][k - cnt(s)]),我们枚举由上一行哪个状态(z)转移而来,(cnt(x))表示二进制下数(x)内(1)的总数(也就是状态(s)的国王个数)
最后一点,边界:
(f[1][s][cnt(s)] = 1),这个应该很好理解吧
最后为了加速,我们可以先预处理出可能的状态(比如一行内两个国王紧挨着肯定是不行的)
最后奉上蒟蒻的代码:
Note:代码里的s是状态的编号
#include <cstdio>
using namespace std;
const int maxn = 10;
const int maxs = 1 << 10;//方案总数
inline int cnt(int x){//统计二进制下1的数量
int ret = 0;
while(x){
if(x & 1)ret++;
x >>= 1;
}
return ret;
}
inline bool check(int x){//判断方案x是否可行(用于预处理)
if(x & (x << 1))return false;
if(x & (x >> 1))return false;
return true;
}
int status[maxs],tot;//预处理出的方案,tot为方案总数
long long f[maxn][maxs][maxn * maxn];//dp数组
int N,K,full;
int main(){
scanf("%d %d",&N,&K);
full = (1 << N) - 1;//full全集
for(int i = 0;i <= full;i++)//预处理
if(check(i))
status[++tot] = i;
for(int i = 1;i <= tot;i++)//边界条件
f[1][i][cnt(status[i])] = 1;
for(int i = 2;i <= N;i++)//dp
for(int s = 1;s <= tot;s++)
for(int k = cnt(status[s]);k <= K;k++)
for(int z = 1;z <= tot;z++){//枚举由上一行的状态z转移而来
int x = status[s];
int y = status[z];
if(x & y)continue;//判断当前行状态x与枚举的上一行状态y是否冲突
if(x & (y >> 1))continue;
if(x & (y << 1))continue;
f[i][s][k] += f[i - 1][z][k - cnt(status[s])];
}
long long ans = 0;
for(int i = 1;i <= tot;i++)//最后统计全都加起来
ans += f[N][i][K];
printf("%lld
",ans);
return 0;
}