【题目】
在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。
【输入格式】
共一行,包含两个整数 n 和 k。
1≤n≤10 ,0≤k≤n2
【输出格式】
共一行,表示方案总数,若不能够放置则输出0。
【输入样例】
3 2
【输出样例】
16
方法1:
#include<bits/stdc++.h>
using namespace std;
int n,k; //棋盘行数,国王总数
int cnt; //同一行的合法状态个数
int s[1<<12]; //同一行的合法状态集
int num[1<<12]; //每个合法状态包含的国王数
long long f[12][144][1<<12];
//f[i,j,a]表示前i行放了j个国王,第i行第a个状态时的方案数
int main()
{
cin>>n>>k;
//找出一行的合法状态和每个合法状态包含的国王数
for(int i=0; i<(1<<n); i++){ //枚举一行的所有状态
if((i&i<<1)==0){ //如果不存在连续1
s[cnt++]=i; //保存一行的合法状态i
for(int j=0; j<n; j++)
num[i]+=(i>>j&1);//统计每个合法状态包含的国王数
}
}
//从第i-1行向第i行状态转移
f[0][0][0]=1; //不放国王也是一种方案
for(int i=1; i<=n+1; i++) //枚举行
for(int j=0; j<=k; j++) //枚举国王数
for(int a=0; a<cnt; a++) //枚举第i行的合法状态
for(int b=0; b<cnt; b++) //枚举第i-1行的合法状态
{
int c=num[s[a]]; //第i行第a个状态的国王数
//可以继续放国王,同列不攻击,斜对角不攻击
if((j>=c)&&!(s[b]&s[a])&&!(s[b]&(s[a]<<1))&&!(s[b]&(s[a]>>1)))
f[i][j][a]+=f[i-1][j-c][b]; //从第i-1行向第i行转移
}
cout<<f[n+1][k][0]<<endl; //第n+1行什么都不放,相当于只在1~n行放国王
return 0;
}
方法2:
#include<bits/stdc++.h>
using namespace std;
long long f[12][144][1<<12];//f[i,j,k]前i行,j个国王,第i行状态为k时的方案数
int t,s[1<<12];
int num[1<<12];
int p[1<<12],h[1<<12][1<<12];//
int n,m;
int main()
{
cin>>n>>m;
//预处理1:同行合法化
for(int i=0;i<(1<<n);i++){
if((i&i<<1)==0){
s[t]=i; //合法状态
for(int j=0;j<n;j++)
num[i]+=(i>>j&1); //合法状态的国王数
//printf("s[%d]=%d c[%d]=%d\n",t,i,i,num[i]);
t++;
}
}
//预处理2:相邻行合法化
for(int i=0; i<t; i++){
for(int j=0; j<t; j++){
int a = s[i];
int b = s[j];
if(!(a&b)&&!(a&b>>1)&&!(a&b<<1)){ //相邻行状态a和b:同列不是1,邻列不是1
h[i][p[i]]=j; //如果这两个状态可以满足条件即可以做邻居
//printf("%d %d h[%d][%d]=%d\n",a,b,i,p[i],j);
p[i]++;
}
}
}
f[0][0][0]=1;
for(int i=1;i<=n+1;i++)
for(int j=0;j<=m;j++)
for(int a=0;a<t;a++)
for(int b=0;b<p[a];b++){
int c=num[s[a]];
if(j>=c){
f[i][j][a]+=f[i-1][j-c][h[a][b]];
//printf("%d %d: f[%d %d %d]=%d f[%d %d %d]=%d\n",a,b,i,j,a,f[i][j][a],i-1,j-c,h[a][b],f[i-1][j-c][h[a][b]]);
}
}
cout<<f[n+1][m][0]<<endl;
return 0;
}
方法3:
#include<bits/stdc++.h>
using namespace std;
const int N = 12;
int n,k; //棋盘行数,国王总数
vector<int> s; //同一行的合法状态集
vector<int> h[1<<N]; //每个状态可以转移到的其他状态的集合
int num[1<<N]; //每个合法状态包含的国王数
long long f[N][N*N][1<<N];
//f[i,j,a]表示前i行放了j个国王,第i行第a个状态时的方案数
int main()
{
cin>>n>>k;
//预处理阶段1:同一行合法化
for(int i=0; i<(1<<n); i++){ //枚举一行的所有状态
if(!(i&i>>1)){ //如果不存在连续1
s.push_back(i); //i合法
for(int j=0;j<n;j++)
num[i]+=(i>>j&1); //合法状态的国王数
printf("%d num[%d]=%d\n",i,i,num[i]);
}
}
//预处理阶段2:相邻行合法化
for(int i=0; i<s.size(); i++){
for(int j=0; j<s.size(); j++){
int a = s[i], b = s[j];
if(!(a&b) && !(a&b>>1) && !(a&b<<1)){ //相邻行状态a和b:同列不攻击,斜对角不攻击
h[i].push_back(j); //如果这两个状态可以满足条件即可以做邻居
printf("%d %d h[%d]=%d\n",a,b,i,j);
}
}
}
//状态转移:从第i-1行向第i行转移
f[0][0][0]=1; //不放国王也是一种方案
for(int i=1; i<=n+1; i++) //枚举行
for(int j=0; j<=k; j++) //枚举国王数
for(int a=0; a<s.size(); a++) //枚举第i行的合法状态
for(int b=0; b<h[a].size(); b++){ //枚举所有相邻行合法状态,只有相邻行合法才能从前一行转移过来
int c=num[s[a]]; //第i行的所有行合法状态国王数
if(j>=c) //可以继续放国王
f[i][j][a]+=f[i-1][j-c][h[a][b]]; //从第i-1行向第i行转移
}
cout<<f[n+1][k][0]<<endl; //第n+1行什么都不放,相当于只在1~n行放国王
return 0;
}