题解 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

总结归纳

多做,多思考

posted @ 2025-07-29 16:40  枯骨崖烟  阅读(10)  评论(0)    收藏  举报