题解 luogu.P1896 [SCOI2005] 互不侵犯
题目
luogu.P1896 [SCOI2005] 互不侵犯
状压DP中非常经典的一道例题。
本文介绍这题的解法。
状压DP初学时确实难(笔者的感觉)
题意建模
运用状态压缩,表示这些状态。
我们定义:对于某一行的所有格子,每个格子视为一个数(取值为0/1),代表这个格子是否放了。
那么现在只要在约束条件下讨论计数。
算法分析
定义状态
\(f(i,j,k)\) 表示处理了前 \(i\) 行,第 \(i\) 行的状态为 \(j\),放置了 \(k\) 个国王的方案数。
考虑转移
在不考虑约束的情况下(事实上,这里忽略,下面再详细说),应有:
\(f(i,j,k)+=f(i-1,j-num[k],l)\) ,这里下面结合代码研究。
预处理与状态合法性判断
首先,对于这一行的 \(x\) ,以及上一行的 \(y\) ,这两个不能冲突,所以应有:
inline bool check(int x,int y)//当前行,上一行
{
if(x&y) return false;
if(y&(x<<1)) return false;
if(y&(x>>1)) return false;
if((y>>1)&y) return false;
return true;
}
另外,令num[k]表示当前行放了多少个国王,这可以预处理出来,降低时间复杂度:
inline int cal(int x)
{
int ans=0;
while(x)
{
if(x&1) ans++;
x>>=1;
}
return ans;
}
原理就是该状态二进制表示有多少个1。
参考代码
#include<iostream>
#define int long long
using namespace std;
const int N=10;
int dp[N<<1][N*N<<1][1<<N],num[1<<N];
int n,m;
inline int cal(int x)
{
int ans=0;
while(x)
{
if(x&1) ans++;
x>>=1;
}
return ans;
}
inline bool check(int x,int y)//当前行,上一行
{
if(x&y) return false;
if(y&(x<<1)) return false;
if(y&(x>>1)) return false;
if((y>>1)&y) return false;
return true;
}
signed main()
{
cin>>n>>m;
dp[0][0][0]=1;
int all=(1<<n)-1;
for(int i=0;i<=all;i++) num[i]=cal(i);
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k<=all;k++)
{
if(num[k]>j) continue;
if(k&(k>>1)) continue;
for(int l=0;l<=all;l++)
{
if(!check(k,l)) continue;
dp[i][j][k]+=dp[i-1][j-num[k]][l];
}
}
int ans=0;
for(int i=0;i<=all;i++) ans+=dp[n][m][i];
cout<<ans<<endl;
return 0;
}
细节实现
经典例题,只能慢慢做。事实上,很不习惯。所以只能写几篇题解巩固一下。
- 运算符的优先级
- 注意开
long long
总结归纳
多做,多思考

浙公网安备 33010602011771号