动态规划之状态压缩(从入门到入土)

一.基本思想

动态规划中最难的一类题之一,随随便便拿出一题都是提高+省选-

其主要思想是二进制位运算
举个例子:
一条街上有n个路灯,路灯只有亮与不亮两种状态。
如果去描述这n个路灯的状态呢???
用数组描述显然可以,但有没有其他的方法???
二进制下的一个整数 k !!!
例如:
n等于8,k=183,k=10110111(2)
那么整数 k 就可以表示第123568个路灯亮,其余路灯不亮
其他功能怎么办???
比如判第i个灯是否亮,关闭第i个灯,如何关闭多个灯之类的

位运算来啦!!!
这个表格仅供参考,直接看最代码里最常用的几条即可

基本功能表

功能 实例 位运算
去掉最后一位 (101101->10110) x>>1
在最后加一个0 (101101->1011010) x<<1
在最后加一个1 (101101->1011011) (x<<1)+1
把最后一位变成1 (101100->101101) x or 1
把最后一位变成0 (101101->101100) (x or 1) - 1
最后一位取反 (101101->101100) x xor 1
把右数第i位变成1 (101001->101101,k=3) x or (1 << (i-1))
把右数第i位变成0 (101101->101001,k=3) x and not(1 << (i-1))
右数第i位取反 (101001->101101,k=3) x xor (1 >> (k-1))
取末三位 (1101101->101) x and 7
取末k位 (1101101->1101,k=5) x and (1 >>k-1)
取右数第k位 (1101101->1,k=4) x >>(k-1) and 1
把末k位变成1 (101001->101111,k=4) x or (1 >>k-1)
末k位取反 (101001->100110,k=4) x xor (1 >>k-1)
把右边连续的1变成0 (100101111->100100000) x and (x+1)
把右起第一个0变成1 (100101111->100111111) x or (x+1)
把右边连续的0变成1 (11011000->11011111) x or (x-1)
取右边连续的1 (100101111->1111) (x xor (x+1)) >>1

个人总结最常用
以下的第i位均为从右起第i位

#include<bits/stdc++.h>
using namespace std;

int main(){
    
    
	
	if(k&(1<<(i-1)));//判断第i位是否为1 
	k=k|(1<<(i-1));//将第i位为变成1 
	k=k&(!(1<<(i-1)));//将第i位为变成0 
	
	if(i&j);//状态i与状态j有重复
	if((i<<1)&j);//状态i与状态j存在右上左下重复,即i的第x位与j的第x+1位有重复 
	if(i&(j<<1));//状态i与状态j存在左上右下重复,即i的第x+1位与j的第x位有重复
	if(j&(j<<1));//状态j自身存在连续1
	
	if(j&(~a[i]));//状态j在不该存在1的地方存在了1 
	 
	return 0;
}

二.开始刷题

例题1

f[i][j][s]表示考虑从第1行到第i行,第i行为j状态下,一共放了s个国王的方案总数。
设:j为第i行的状态,p为第i-1行的状态
状态转移:f[i][j][s]+=f[i-1][p][s-king[j]]

#include<bits/stdc++.h>
using namespace std;

long long n,k;
long long f[12][1000][100],king[1000];
//f[i][j][s]表示考虑从第1行到第i行,第i行为j状态下,一共放了s个国王的方案总数


void init() {
    
    
	int tot=1<<n;
    
    //预处理每种状态下国王共摆放的数量
	for(int j=0; j<tot; j++) {
    
    
		int t=j;
		while(t) {
    
    
			king[j]+=t%2;
			t/=2;
		}
	}
    
    //预处理第1行摆放的每种状态下的方案数
	for(int j=0; j<tot; j++) {
    
    
		if(j&(j<<1))continue;
		f[1][j][king[j]]=1;
	}
}


void solve() {
    
    
	int tot=1<<n;
	for(int i=2; i<=n; i++) {
    
    
		for(int j=0; j<tot; j++) {
    
     //枚举第i行的状态
			if(j&(j<<1))continue;//j状态下无法满足第i行的国王分布情况
			for(int p=0; p<tot; p++) {
    
     //枚举第i-1行的状态
				//状态j与状态p矛盾
				if(j&(p<<1))continue;
				if(j&(p>>1))continue;
				if(j&p)continue;
                
                //状态j与状态p不矛盾,继承方案
				for(int s=k;s>=king[j];s--){
    
    
					f[i][j][s]+=f[i-1][p][s-king[j]];
				}
			}
		}
	}
	long long ans=0;
	for(int j=0;j<tot;j++)ans+=f[n][j][k];
	cout<<ans;
}
int main() {
    
    
	cin>>n>>k;
	init();
	solve();
	return 0;
}

例题2

f[i][j]表示处理到第i行,第i行种植状态为j的情况的方案总数
设:j为第i行的状态,p为第i-1行的状态
状态转移方程:f[i][j]+=f[i-1][p]

#include<bits/stdc++.h>
using namespace std;

long long n,m,mod=100000000;
long long f[15][10000],a[15];

void init(){
    
    
    int tot=1<<m;
	
	//预处理第1行种植的每种状态下的方案数 
	for(int j=0;j<tot;j++){
    
     
		if(j&(j<<1))continue;//自己行相邻矛盾 
		if(j&(~a[1]))continue;//非法种植矛盾 
		f[1][j]=1;
	}
}
void solve(){
    
    
	for(int i=2;i<=n;i++){
    
    
		for(int j=0;j<tot;j++){
    
    //枚举第i行的状态 
			if(j&(j<<1))continue;//自己行相邻矛盾 
			if(j&(~a[i]))continue;//非法种植矛盾 
			for(int p=0;p<tot;p++){
    
    //枚举第i-1行的状态 
				if(p&(p<<1))continue;//自己行相邻矛盾 
				if(p&(~a[i-1]))continue;//非法种植矛盾 
				if(j&p)continue;//上下行相邻矛盾 
		
                //状态j与状态p合法,转移 
				f[i][j]=(f[i][j]+f[i-1][p])%mod; 
			}
		}
	} 
	
	long long ans=0;
	for(int j=0;j<tot;j++)ans=(ans+f[n][j])%mod;
	cout<<ans; 
} 
int main(){
    
    
	int x;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
    
    
    	for(int j=1;j<=m;j++){
    
    
    		cin>>x;
    		a[i]=a[i]*2+x;//处理第i行的合法种植状态
		}
	}
	solve();
	return 0;
} 

例题3

稍稍有点难度,难度在于要考虑上上行的状态,多一维维护就好了
设:j为第i行的状态,k为第i-1行的状态,p为第i-2行的状态
状态转移方程:f[i][j][k]=max(f[i][j][k],f[i-1][k][p]+sum+king[j])
显然MLE,滚动数组优化一下就好了,用%滚动,那么存储从0~n-1

#include<bits/stdc++.h>
using namespace std;

int n,m,king[1200],f[4][1200][1200],a[1200],ans;
//f[i][j][k]表示考虑到从第一行到第i行,第i行状态为j,第i-1行状态是k的情况下的摆放炮兵的总数量

void init() {
    
    
	int tot=1<<m;

	//预处理状态j所摆放的炮兵总数量
	for(int j=0; j<tot; j++) {
    
    
		int t=j;
		while(t) {
    
    
			king[j]+=t%2;
			t/=2;
		}
	}


	//预处理第1(2)行摆放的每种状态下的方案数
	for(int j=0; j<tot; j++) {
    
    
		if(j&(j<<1))continue;
		if(j&(j<<2))continue;
		if(j&(~a[0]))continue;
		for(int k=0; k<tot; k++) {
    
    
			if(k&(k<<1))continue;
			if(k&(k<<2))continue;
			if(k&(~a[1]))continue;
			if(j&k)continue;
			f[1][k][j]=king[j]+king[k];
		}
	}
}

void solve() {
    
    
	int tot=1<<m;

	for(int i=2; i<n; i++) {
    
     
		for(int j=0; j<tot; j++) {
    
    //枚举第i行状态 
			if(j&(j<<1))continue;
			if(j&(j<<2))continue;
			if(j&(~a[i]))continue;
			for(int k=0; k<tot; k++) {
    
    //枚举第i-1行的状态 
				if(k&(k<<1))continue;
				if(k&(k<<2))continue;
				if(k&(~a[i-1]))continue;
				if(j&k)continue;
				for(int p=0; p<tot; p++) {
    
    //枚举第i-2行的状态 
					if(p&(p<<1))continue;
					if(p&(p<<2))continue;
					if(p&(~a[i-2]))continue;
					if(k&p)continue;
					if(j&p)continue;
					
					//满足摆放条件,状态转移 
					f[i%3][j][k]=max(f[i%3][j][k],f[(i-1)%3][k][p]+king[j]);
				}
			}
		}
	}
	for(int j=0; j<tot; j++) {
    
    
		for(int k=0; k<tot; k++)ans=max(ans,f[(n-1)%3][j][k]);
	}
	cout<<ans;
}

int main() {
    
    
	int x;
	char c;
	cin>>n>>m;
	for(int i=0; i<n; i++) {
    
    
		for(int j=1; j<=m; j++) {
    
    
			cin>>c;
			if(c=='P')x=1;
			else x=0;
			a[i]=a[i]*2+x;
		}
	}
	init();
	solve();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43602607/article/details/110956214