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

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

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

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

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

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

 

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

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

           

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

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

             见图

                

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

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

     

for(int i=0;i<=(1<<n)-1;i++){
    	if(合法){
    	 cnt++;
    	 zt[cnt]=i;
	  } 
	}

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

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

            

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

 

 

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

      

              本行的左上有国王。

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

         

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

         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<iostream>
#include<cstdio>
#include<algorithm>
#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<<n)-1;i++){
    	if(!judge(i)){
    	 cnt++;
    	 zt[cnt]=i;
	  } 
	}
	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((!work2(zt[l],zt[j]))&&(k+num[zt[l]]<=m)){
	   	 	  		dp[i+1][l][k+num[zt[l]]]+=dp[i][j][k];
			   }	   
		 }
	   } 
	for(ll i=1;i<=cnt;i++)
	 ans+=dp[n][i][m];
	printf("%lld",ans);    
	return 0;
}

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

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

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

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

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

  

#include<iostream>
#include<cstdio>
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)&a))&&(((x<<(w+1))&a))) return 1;
		if(((x<<(w-1))&a)&&(!((x<<w)&a))&&(((y<<(w+1))&a))) return 1;
		w++;
	}
	return 0;
}
int work2(int x,int y,int z){
	int w=1;
	int a=(1<<(n-1));
	while(w<=n){
		if(((z>>(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<<y)-1;i++) dp[1][0][i]++;
	for(int i=0;i<=(1<<y)-1;i++)
	 for(int j=0;j<=(1<<y)-1;j++)
	  if(!work(i,j)) dp[2][i][j]+=dp[1][0][i];
	for(int i=3;i<=x;i++)
	  for(int j=0;j<=(1<<y)-1;j++)
	   for(int k=0;k<=(1<<y)-1;k++)
	    for(int l=0;l<=(1<<y)-1;l++)
	     if(!work(k,l)&&!work(j,k)&&!work2(j,k,l))
	      dp[i][k][l]+=dp[i-1][j][k],dp[i][k][l]%=p;
	int ans=0;    
	for(int i=0;i<=(1<<y)-1;i++)
	 for(int j=0;j<=(1<<y)-1;j++)
	  ans+=dp[x][i][j],ans%=p;
	printf("%d",ans%p);        
	return 0;
}

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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值