[bzoj1087] little kings
题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
输入格式
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式
一行,一个整数,表示答案。
样例数据
input
3 2
output
16
数据规模与约定
N<=10
看到数据范围,首先考虑暴搜和状压
我们用f[i][k][j]表示当前在第i行,已经放置了k个国王,且当行状态为j的方案数
首先我们可以枚举所有的状态,并将没有相邻两个1的状态存入一个集合S
然后我们预处理一下从每一个S中的状态i的上一行可能是那些状态
j是i的上一行当且仅当
1:没有任意一位上i和j都是1,因为国王会攻击到他上面(或下面),及i&j==0;
2 :没有任意相邻两位上有两个1
例如 i: 1 0 是不合法的 i: 1 0 是合法的
j: 0 1 j: 0 1
因为国王会攻击到他左上左下右上右下的格子,这一判断即check(S[i]|S[j]); 其中check为判断是否有两个相邻的1
所以预处理就是
for(int i=0;i<S.size();i++)
{
for(int j=0;j<S.size();j++)
{
if(check(S[i]|S[j])&&(S[i]&S[j])==0)
To[S[i]].push_back(S[j]);
}
}
这就表示状态S[i]的上一行可以是S[j];
那状态转移方程就很简单了
f[i][k][j]=f[i][k][j]+f[i-1][k-count[j][l];
其中count[j]表示状态j的二进制数里有几个1(即对应了有几个国王),这个可以预处理出来
所以我们一层循环枚举i,一层循环枚举k,一层循环枚举j,在通过预处理出来的To数组求出l
初值f[0][0][0]=1;
完整代码如下
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
int Count[1<<10];
vector<int> S;
vector<int>To[1<<10];
ll f[12][120][1<<10];
bool check(int x)
{
for(int i=0;i<n;i++)
{
if((x>>i&1)&&(x>>(i+1)&1))
return false;
}
return true;
}
int main()
{
freopen("king.in","r",stdin);
freopen("king.out","w",stdout);
cin>>n>>m;
for(int i=0;i<1<<n;i++)
{
if(check(i)) S.push_back(i);
for(int j=0;j<n;j++)
{
if(i>>j&1)
Count[i]++;
}
}
for(int i=0;i<S.size();i++)
{
for(int j=0;j<S.size();j++)
{
if(check(S[i]|S[j])&&(S[i]&S[j])==0)
To[S[i]].push_back(S[j]);
}
}
f[0][0][0]=1;
for(int i=1;i<=n+1;i++)
{
for(int k=0;k<=m;k++)
{
for(int t=0;t<S.size();t++)
{
int a=S[t];
for(int j=0;j<To[a].size();j++)
{
int b=To[a][j];
if(k>=Count[a])
{
f[i][k][a]+=f[i-1][k-Count[a]][b];
}
}
}
}
}
cout<<f[n+1][m][0];
return 0;
}