Description
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
Input
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
Output
所得的方案数
很明显(N leq 9)状压DP啊 qwq.
这里有限制,我们只能放(K)个国王,并且如果一个格子有国王,其周围八个格子都不能放.
设状态(f[i][j][k])代表前(i)行中第(i)行为(j)状态下共放了(k)个国王的方案数.
状态转移的话,我们当前行显然已经放了国王.
因此,转移可以很容易想到.
[f[i][j][l+calc(j)]+=f[i-1][k][l]
]
其中(l)为枚举的上一行所放的国王的个数,(k)为枚举的上一行的状态,(calc(j))为计算(j)状态下有多少个国王被放置.
判断合法与否的话,只需要判断一下当前(j)状态与(k)状态(&)起来是否为零。
如何判断状态合法
判断是否(j)状态的某一位置右上方有无国王.
[(j>>1)&k==0
]
同理左上方
[(j<<1)&k==0
]
正上方
[j&k==0
]
这几个方向是相对而言的.且我们从第(1)行到第(n)行放置的话,每次判断是否合法达到了判断6个方向的效果.
判断左右两侧当然是最简单的了
[j&(j>>1)==0 && j&(j<<1)==0
]
代码
#include<cstdio>
#define int long long
#define R register
using namespace std;
int n,m,f[10][2048][108];
int lim,ans;
inline bool ok(int i)
{
return ((i&(i<<1))==0 and (i&(i>>1))==0);
}//相邻方向.
inline int calc(int x)
{
int res=0;
for(R int i=0;(1<<i)<=x;i++)
res+=(bool)(x&(1<<i));
return res;
}
signed main()
{
scanf("%lld%lld",&n,&m);
f[0][0][0]=1;
lim=(1<<n)-1;
for(R int i=1;i<=n;i++)
{
for(R int j=0;j<=lim;j++)
{
if(!ok(j))continue;
for(R int k=0;k<=lim;k++)
{
if(!ok(k))continue;
if((j&k)==0 and ((j<<1)&k)==0 and ((j>>1)&k)==0)
{
R int now=calc(j);
for(R int l=0;l<=m;l++)
f[i][j][l+now]+=f[i-1][k][l];
}
}
}
}
for(R int i=0;i<=lim;i++)
ans+=f[n][i][m];
printf("%lld
",ans);
}
可以滚动数组滚掉第(1)维,切在枚举(l)的时候,第三维可能会超内存,因此要开大一点.当然也可以判断一下(l+now leq m)