洛谷P1896[SCOI2005]互不侵犯(状压DP)
题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
注:数据有加强(2018/4/25)
输入输出格式
输入格式:
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式:
所得的方案数
输入输出样例
输入样例#1:
3 2
输出样例#1:
16
\(Solution:\)
\(n<=9\),所以我们可以用状压DP解决这题
由于当前状态只会对下一行造成影响,所以DP方程中要记一下这个状态,状压一下就好了;另外你只能放 \(k\) 个国王,所以你还需要一维来记录当前放的国王总数。
首先我们可以处理出每一行所有可能的状态,记下状态并记下放了多少国王,这个可以用DFS实现:
void dfs(int p,int v,int c){
if(p>=n)
{
//h[i]数组表示第i种可能的情况中放了多少国王
//g[i]表示第i种可能的情况的状态
h[++cnt]=v,g[cnt]=c;
return;
}//这里我把可能状态的总数用cnt记录了下来
dfs(p+1,v,c);//如果不在p号位置放国王,就继续找
dfs(p+2,v+1,c+(1<<p));//如果在p号位置放,那么p+1号位置就放不了了
}
设 \(f[i][j][k]\) 表示已经到了第 \(i\) 行,总共放了 \(j\) 个国王,且当前状态为第 \(k\) 种可能状态的方案数。转移方程就是:
for(int i=1;i<=cnt;i++) f[1][h[i]][i]=1;//初始化第一行
for(int i=2;i<=n;i++)
for(int j=1;j<=cnt;j++)//枚举可能的状态编号
for(int k=1;k<=cnt;k++)//同上
{
if(g[j]&g[k]) continue;
if(g[j]&(g[k]>>1)||g[j]&(g[k]<<1))
continue;//判断合法性
for(int p=m;p>=h[j];p--)//转移
f[i][p][j]+=f[i-1][p-h[j]][k];
}
最后附上总代码:
#include<bits/stdc++.h>
#define il inline
#define int long long//这题要开long long
using namespace std;
const int N=1100;
il int read(){
int f=1,w=0;char c=0;
while(!isdigit(c))
{
if(c=='-') f=-1;
c=getchar();
}
while(isdigit(c)) w=w*10+(c^48),c=getchar();
return f*w;
}
int n,m,cnt,h[N],g[N],f[10][100][N],ans;
void dfs(int p,int v,int c){
if(p>=n)
{
h[++cnt]=v,g[cnt]=c;
return;
}
dfs(p+1,v,c);
dfs(p+2,v+1,c+(1<<p));
}
main(){
n=read(),m=read();
dfs(0,0,0);//处理每一行的可能的状态
for(int i=1;i<=cnt;i++) f[1][h[i]][i]=1;
for(int i=2;i<=n;i++)
for(int j=1;j<=cnt;j++)
for(int k=1;k<=cnt;k++)
{
if(g[j]&g[k]) continue;
if(g[j]&(g[k]>>1)||g[j]&(g[k]<<1))
continue;
for(int p=m;p>=h[j];p--)
f[i][p][j]+=f[i-1][p-h[j]][k];
}
//DP过程
for(int i=1;i<=cnt;i++) ans+=f[n][m][i];//计算答案
cout<<ans<<endl;
return 0;
}