题目描述
在 N × N N \times N N×N 的棋盘里面放 K K K 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 8 8 个格子。
输入格式
只有一行,包含两个数 N , K N,K N,K。
输出格式
所得的方案数
样例输入 #1
3 2
样例输出 #1
16
提示
数据范围及约定
对于全部数据, 1 ≤ N ≤ 9 1 \le N \le 9 1≤N≤9, 0 ≤ K ≤ N × N 0 \le K \le N\times N 0≤K≤N×N。
分析
看数据范围,一眼状压。
关键看阶段和状态怎么设计,一般来讲都是一行一行向下转移,而且影响也只是到下一行,转移显然跟国王个数有关,所以就设 f [ i ] [ j ] [ s ] f[i][j][s] f[i][j][s] 为填了前 i i i 行,已经用了 j j j 个国王,当前行的状态为 s s s 的方案总数。
从上一行转移过来:
f
[
i
]
[
j
]
=
Σ
f
[
i
−
1
]
[
j
−
c
n
t
[
i
]
]
[
s
′
]
f[i][j]=\Sigma f[i-1][j-cnt[i]][s']
f[i][j]=Σf[i−1][j−cnt[i]][s′]
其中cnt[i]是当前状态1的个数,可以预处理出来。
s
′
s'
s′ 是所有合法状态,判断合法状态只需要左移右移判断“&”是否为0就行。
一个优化,因为同一行当中两个1相邻是不允许的,所以可以预处理同一行中间可行的所有状态,大大缩减枚举。
上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,k;
ll f[10][90][1<<10],ans;
ll cnt[1<<10],ok[1<<10],tot;
int main()
{
cin>>n>>k;
for(int i=0;i<=(1<<n)-1;i++)
{
int t=i;
while(t)
{
if(t&1) cnt[i]++;
t>>=1;
}
if(((i<<1)&i)==0&&((i>>1)&i)==0) ok[++tot]=i;//预处理
}
f[0][0][0]=1;
for(int i=1;i<=n;i++)
{
for(int h=1;h<=tot;h++)
{
int s1=ok[h];//这一行
for(int l=1;l<=tot;l++)
{
int s2=ok[l];//上一行
if((s1&s2)==0&&((s1<<1)&s2)==0&&((s1>>1)&s2)==0)
{
for(int j=0;j<=k;j++)
{
if(j-cnt[s1]>=0)
f[i][j][s1]+=f[i-1][j-cnt[s1]][s2];
}
}
}
}
}
for(int i=1;i<=tot;i++)
{
ans+=f[n][k][ok[i]];
}
cout<<ans;
return 0;
}