专题 状压dp(状态压缩动态规划)(洛谷的P1896 [SCOI2005]互不侵犯 详解)

       简而言之,状态压缩就是用进制数(可以是二进制,三进制等等)表示状态,然后进行dp。

       例如,洛谷的P1896 [SCOI2005]互不侵犯:

             题目描述:https://www.luogu.org/problemnew/show/P1896

                题意大致就是,在一个棋盘里放一定数量的国王,一个国王的周围没有国王,询问方案数。

              这是个简单的状压dp(真简单呐~)

 

       注:&运算法则,上下如果都是1,则为1.

              我们可以用1表示在棋盘的格点上表示有国王,0表示在棋盘的格点上没有国王。因为每个格点只有两种状态,所以我们可以用二进制数来表示每一行的状态。例如:

           专题 状压dp(状态压缩动态规划)(洛谷的P1896 [SCOI2005]互不侵犯 详解)_第1张图片

      假设这是第i行国王的摆放情况,我们可以用10(1*2^1+1*2^4)来表示这一行的状态. 不难知道,一共有2^n种状态,也就是(0~2^n-1)。

     我们可以先判断一行中的状态合不合法,若a&(a>>1)或者a&(a<<1)不为零,则此状态不合法。为什么呢??

             见图

                专题 状压dp(状态压缩动态规划)(洛谷的P1896 [SCOI2005]互不侵犯 详解)_第2张图片

     不难看出如果一个国王左右相邻有国王,则a&(a>>1)或者a&(a<<1)不为零。

    这样我们就可以预处理行内的所有合法状态,然后直接用合法状态进行转移(优化时间复杂度):

     

for(int i=0;i<=(1<

      横行判断完之后,判断纵行合不合法。不妨先判断上下两行合不合法(令上一行为a,本行为b)

             如果(a&b)不为零,则不合法。

            专题 状压dp(状态压缩动态规划)(洛谷的P1896 [SCOI2005]互不侵犯 详解)_第3张图片

        上下紧挨的有国王不合法。

 

 

        如果 ((a>>1)&b)不为零,则不合法。

      专题 状压dp(状态压缩动态规划)(洛谷的P1896 [SCOI2005]互不侵犯 详解)_第4张图片

              本行的左上有国王。

         同样,如果右上有国王,也不合法。

         

         条件判断完之后考虑状态转移:

         dp[i][j][k]  i表示安放的第几行,j表示第i行的状态,k表示已经安放的国王数。

	for(ll i=0;i<=n-1;i++)//枚举行数
      for(ll j=1;j<=cnt;j++)//预处理完了的每行的合法状态,枚举本行的合法状态
       for(ll k=0;k<=m;k++)//枚举已经安放的国王数
	   {
	   		for(ll l=1;l<=cnt;l++){ //枚举下一行
	   	 	  	if(合法){
	   	 	  		dp[i+1][l][k+(l行的国王数)]+=dp[i][j][k];//下一行以l为状态,k+(l行的国王数)的已经安放的国王的方案可以被本行以j为状态,k个国王所转移
			   }	   
		 }
	   } 

     最后输出dp[n][(可以以任意合法状态结尾)][m]就行了

    

for(ll i=1;i<=cnt;i++)
	 ans+=dp[n][i][m];
  printf("%lld",ans); 

     如需参考,详见全部代码:

   

#include
#include
#include
#define ll long long
using namespace std;
long long n,k,dp[10][1000][100],m,num[1000],cnt,zt[1000];
long long work(long long x){
	long long ans=0;
	while(x!=0){
	  if(x%2)  ans++;
	  x=x/2;
	}
	return ans;
}
long long judge(long long a){
	if(a&(a<<1)||a&(a>>1)) return 1;
	return 0;
}
long long work2(long long a,long long b){
	if(!(a&(b<<1))&&!(a&(b>>1))&&!(a&b)) return 0;
	return 1; 
}
int main(){
	long long ans=0;
	scanf("%lld%lld",&n,&m);
	for(long long i=1;i<=1000;i++)
	 num[i]=work(i);
	dp[0][1][0]=1;
	cnt=0;
    for(ll i=0;i<=(1<

 本题算是一个比较普通的状压吧,条件比较好想。

 以上算是个引子:(前方高能!)

  状压的动态规划: 一般的动态规划,也就是从整体中提取两三个关键的信息,依此划分阶段使问题不具备后效性及最优子结构的性质。但有时,保存的信息不足以进行决策的时候,许多元素都会直接影响到决策,都需要被考虑到。为每一个元素的状态都开一维数组来存储肯定是行不通的,于是就需要有一种便于操作的几下各个元素的状态,即进行状态压缩存储,也就有了状态压缩的动态规划(需细细品味!!!!!!!)。

 也有比较复杂的条件判断:例如洛谷P5005 中国象棋—摆上马(也不算是很复杂)

  此题还需要考虑(俗话说的)别马脚(由于好写但是很难说,所以直接撂下代码)

  

#include
#include
using namespace std;
const int p=1e9+7;
int dp[105][70][70],n;
int work(int x,int y){
	int w=1;
	int a=(1<<(n-1));
	while(w<=n){
		if(((y>>(w-1))&1)&&(!((y>>w)&1))&&(((x>>(w+1))&1))) return 1;
		if(((x>>(w-1))&1)&&(!((x>>w)&1))&&(((y>>(w+1))&1))) return 1;
		if(((y<<(w-1))&a)&&(!((y<>(w-1))&1)&&(!((y>>(w-1))&1))&&(((x>>(w))&1))) return 1;
		if(((x>>(w-1))&1)&&(!((y>>(w-1))&1))&&(((z>>(w))&1))) return 1;
		if(((z<<(w-1))&a)&&(!((y<<(w-1))&a))&&(((x<<(w))&a))) return 1;
		if(((x<<(w-1))&a)&&(!((y<<(w-1))&a))&&(((z<<(w))&a))) return 1;
		w++;
	}
	return 0;
}
int main(){
	int x,y;
	scanf("%d%d",&x,&y);
	n=y;
	for(int i=0;i<=(1<

   当然这不是满分程序,由于题开的内存太小,所以要开滚动数组,但这么写代码好理解。

  类似的题还有很多,就不一 一列举了

你可能感兴趣的:(编程)