题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=492
题目大意:在n*n的棋盘上放k个国王(可攻击相邻的8个格子),求使它们无法互相攻击的方案数。输入n和k(0<n<=10,0<k<=n^2)。
解题思路:数据很小,可以用状态压缩。
首先 对于每行所有状态,我们可以 先处理出 有效状态(即在一行的所有国王不会相互攻击的状态),用 sta[]表示,其中 ct 表示有效状态总数。
其次 对于每种合法状态,我们要计算出在该种状态下,放的国王数,用cnt[]表示。
我们可以定义 dp[i][j][k]为 在第i行的第j个状态 已经放了k个 国王的方案数,则状态转移方程如下:
dp[i][j][k1+cnt[j]]=dp[i][j][k]+dp[i-1][j1][k1];
表示 在第i行的第j个状态 已经放了k个 国王的方案数 是由第i-1行 第j1个状态 已经放了k1个国王的状态转移过来,如果可以转移,则转移到的状态需加上j行cnt[j];
其中 也可以预先处理出相邻两行的合法状态,这样会更快,也可以用滚动数组优化 空间,但下面的代码还是比较朴素的代码。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,k;
long long dp[11][150][105];
int sta[150],cnt[150];
void init()
{
memset(dp,0,sizeof(dp));
memset(sta,0,sizeof(sta));
memset(cnt,0,sizeof(cnt));
}
void solve()
{
int num=(1<<n),ct=0;
for(int i=0; i<num; i++)//合法状态数
if( !(i&(i<<1)) )
sta[ct++]=i;
for(int i=0; i<ct; i++)
{
int cot=0;
for(int j=0; j<n; j++) //每个合法状态的国王个数
if( sta[i]&(1<<j) )
cot++;
if(cot<=k) //预处理第0行
dp[0][i][cot]=1;
cnt[i]=cot;
}
for(int i=1; i<n; i++)
for(int j=0; j<ct; j++)
for(int p=0; p<ct; p++)
{
if( (sta[j]&sta[p]) || (sta[j]&(sta[p]>>1)) || (sta[j]&(sta[p]<<1)) ) continue;
for(int k1=0; k1<=k; k1++)
if( k1+cnt[j]<=k )
dp[i][j][k1+cnt[j]]+=dp[i-1][p][k1];
}
long long ans=0;
for(int i=0;i<ct;i++)
ans+=dp[n-1][i][k];
printf("%lld\n",ans);
}
int main()
{
while(~scanf("%d%d",&n,&k))
{
init();
solve();
}
return 0;
}