题目描述
在 N×NN \times NN×N 的棋盘里面放 KKK 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 888 个格子。
输入格式
只有一行,包含两个数 N,KN,KN,K。
输出格式
所得的方案数
样例输入 #1
3 2
样例输出 #1
16
提示
数据范围及约定
对于全部数据,1≤N≤91 \le N \le 91≤N≤9,0≤K≤N×N0 \le K \le N\times N0≤K≤N×N。
分析
看数据范围,一眼状压。
关键看阶段和状态怎么设计,一般来讲都是一行一行向下转移,而且影响也只是到下一行,转移显然跟国王个数有关,所以就设 f[i][j][s]f[i][j][s]f[i][j][s] 为填了前 iii 行,已经用了 jjj 个国王,当前行的状态为 sss 的方案总数。
从上一行转移过来: f[i][j]=Σf[i−1][j−cnt[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;
}