1087: [SCOI2005]互不侵犯King
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 4064 Solved: 2354
[ Submit][ Status][ Discuss]
Description
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上
左下右上右下八个方向上附近的各一个格子,共8个格子。
Input
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
Output
方案数。
Sample Input
3 2
Sample Output
16
一道状压DP。
状压DP就是可以解决一些状态不易表达的问题。本题中将放国王与不放设置为0和1即可把每一行的状态变为一个01串,再对应二进制转十进制即可表达状态。设f[i][j][s]表示第i行共用了k个国王,且第i行摆法的二进制转十进制为s的方案总数。考虑两个问题,1.如何转移?枚举前一行的状态 2.如何判断状态是否合法?利用位运算的&字符,国王可以攻击八联通方向,而我们更新时只需要关注左上,正上,右上,左,右五个位置,若能攻击到,则水平方向相邻两个位置必有国王,所以我们只需要把两个状态&一下,若结果为0,证明两行不存在国王在国王的正上方,再将某一个左移一位,&一下,再右移一位,&一下,即可保证斜上方也不存在国王可以攻击。
细节有很多地方,需要保证枚举的状态合法,前一行的状态合法,故要有很多&操作。
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
const int MAXN=11;
const int MAXM=520;
long long f[MAXN][MAXN*MAXN][MAXM];
int main()
{
int n,k;
memset(f,0,sizeof(f));
scanf("%d%d",&n,&k);
for (int i=0;i<=(1<<n)-1;i++)
{
if ((i & (i>>1))==0)
{
int x=i,tot=0;
while (x)
{
if (x & 1)
{
tot++;
}
x/=2;
}
f[1][tot][i]=1;
}
}
for (int i=2;i<=n;i++)
{
for (int j=0;j<=(1<<n)-1;j++)
{
for (int p=0;p<=(1<<n)-1;p++)
{
for (int q=0;q<=k;q++)
{
int x=j,y=p,tot1=0,tot2=0;
if ((j&(p>>1))!=0)
{
continue;
}
if ((j & p)!=0)
{
continue;
}
if ((j&(p<<1))!=0)
{
continue;
}
if ((j&(j>>1))!=0)
{
continue;
}
if ((p&(p>>1))==0)
{
while (x)
{
if (x & 1)
{
tot1++;
}
x/=2;
}
if (tot1+q>k)
{
continue;
}
f[i][tot1+q][j]+=f[i-1][q][p];
}
}
}
}
}
long long ans=0;
for (int i=0;i<=(1<<n)-1;i++)
{
ans+=f[n][k][i];
}
cout<<ans;
return 0;
}