互不侵犯----状压dp入门经典题目

题目链接:
https://www.luogu.com.cn/problem/P1896
展开
题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

注:数据有加强(2018/4/25)

分析:
这道题是一个很经典的状压dp,首先我们想一下暴力解法,就是去枚举每一行的所有情况,然后再暴力去判断这种情况是否合法,当然如果暴力枚举我们可以剪枝来缩短时间,但是仍然很慢,所以我们用二进制来储存状态,使用二进制的按位与,或,异或等操作来判定状态的可行性就可以节省很多时间,这也是状压dp的状压的特性,那么dp的特性当然少不了状态转移了,就拿这道题来举例,很容易想到我们应该由上一行去推导下一行的状态,因为上一行的状态会影响下一行的状态,所以我们先枚举第一行的可行状态,然后通过状态转移去找到在之前所有行状态确定的情况下当前行有哪些状态满足条件(这就需要自己推导状态转移方程,也是dp的核心所在),最后把所有可行状态加起来就是答案了.

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int Maxn = 200;
int n,m,tot = 0;
int s[Maxn];//记录一行中可行的方案数
int w[Maxn]; 
ll f[Maxn][Maxn][Maxn]={
    
    0};//f[i][j][k]第i行,第j种方案,意见有k个1的方案数
/*这是求number这个数的二进制有多少个1*/ 
int get_number(int number)
{
    
    
	int ans = 0;
	while(number)
	{
    
    
		ans++;
		number&=(number-1);
	}
	return ans;
}
int main()
{
    
    
	cin>>n>>m;
	int N = 1<<n;
	/*枚举一行中所有的可行状态*/
	for(int i=0;i<N;i++)
	{
    
    
		if(i&(i>>1)) continue;//不能有左右相邻的1 
		s[++tot] = i;//状态可行 
		w[tot] = get_number(i);//看i这种方案有多少个1 
		f[1][tot][w[tot]] = 1;//先初始化第一列的所有情况 
	}
	/*从第二行不断往下递推*/
	for(int i=2;i<=n;i++)
	 for(int j=1;j<=tot;j++)//当前列的方案 
	  for(int k=1;k<=tot;k++)//上一列的方案 
	  {
    
    
	  	  /*三种不可行的方案*/
	  	  if(s[k]&s[j]) continue;//当前位置和正上方都有1
		  if((s[k]<<1)&s[j]) continue;//当前位置和右上方都有1
		  if((s[k]>>1)&s[j]) continue;//当前位置和左上方都有1
		  /*p表示目前已拥有的妻子,从w[j]开始,因为之前最少以拥
		  有的棋子为0个,加上这一行至少也有w[j],但最多不超过m个
		  不然的话就不满足条件了*/
		  for(int p=w[j];p<=m;p++)
		  {
    
    
		  	f[i][j][p]+=f[i-1][k][p-w[j]];//状态转移方程 
		  } 
	  }
	ll ans = 0;
	for(int i=1;i<=tot;i++)
	ans+=f[n][i][m];//最后我们把第n行的所有状态满足且有m个棋子的合法情况加起来就是答案
	
	cout<<ans<<'\n';
	return 0;   
}

Guess you like

Origin blog.csdn.net/TheWayForDream/article/details/116270492