状态压缩dp知识点总结

状态压缩dp

1、经典状态压缩

经典状态压缩dp基于二维空间,不同行之间会产生影响
题目1:洛谷:Corn Fields G

这道题是该类最基础的题,题目只有两个限制条件:草地不能挨着草只能种在肥沃的土地上

对于第一种限制条件,我们可以分类讨论:同行相邻草地、不同行相邻草地。
同行相邻草地:预处理一个state数组,将所有状态遍历一次,一旦遇到二进制中存在相邻1的状态,则将其剔除,最后保留下来的状态就都是符合要求的。
不同行相邻草地:分别枚举两行所有符合“同行相邻草地”要求的状态,设两状态分别为 a b,若a & b != 0,则存在相邻草地,不符合要求,否则符合要求。

对于第二种限制条件,我们可以把每一行的肥沃情况用二进制表示出来,0表示不肥沃,1表示肥沃,在计算某一行时直接将该行状态 & 此二进制数,若不为0则存在草地种在非肥沃土地上的情况,直接 continue 进行下一次循环。

这里可以做一个优化,可以提前将某一状态可以到达的状态用二维数组表达出来,这样在枚举的时候就不必枚举所有状态,大大降低时间复杂度,具体见head数组。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 15,M = 1 << 14,mod = 1e8;
int f[N][M];
int n,m;
int w[N];
vector<int> state;
vector<int> head[M];
bool check(int state){
	for(int i = 0;i < m;i ++)
		if((state >> i & 1) & (state >> i + 1 & 1))
			return false;
	return true;
}
int main(){
	cin >> n >> m;
	for(int i = 1;i <= n;i ++)
		for(int j = 0;j < m;j ++){
			int a;
			cin >> a;
			w[i] += (a == 0) << j;
		}
	for(int i = 0;i < 1 << m;i ++)
		if(check(i))
			state.push_back(i);
	for(int i = 0;i < state.size();i ++)
		for(int j = 0;j < state.size();j ++){
			int a = state[i],b = state[j];
			if(a & b) continue;
			head[i].push_back(j);
		}
	f[0][0] = 1;
	for(int i = 1;i <= n + 1;i ++)
		for(int j = 0;j < state.size();j ++)
			for(auto k : head[j]){
				int a = state[j],b = state[k];
				if(a & w[i] | b & w[i - 1]) continue;
				f[i][j] = (f[i][j] + f[i - 1][k]) % mod;
			}
	cout << f[n + 1][0];
	return 0;
}

题目2:洛谷:互不侵犯

该题相对上一题少了地形限制,多了个数限制,不同行之间的限制也稍加难度。

对于个数限制,我们可以将状态转移数组设为三维(加了一维国王个数),再定义一个cnt数组,cnt[i] 表示状态i中1的数量(即国王的数量),这在状态转移时可以用到,详情可见代码。

相对于上题的相邻格子不能全为草地,该题变为国王周围八个格子不能有其他国王。一行的状态处理和上一题相同,不同行之间的状态处理则稍作改变,设 i - 1 行和 i 行的状态分别为 a,bc = a | b,若 c 中存在相邻的1,则不满足要求。

此题也可用head数组预处理来优化。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 12,M = 1 << 10,K = 105;
long long f[N][K][M];
int n,m;
vector<int> state;
int cnt[M];

bool check(int state){
	for(int i = 0;i < n;i ++)
		if((state >> i & 1) & (state >> (i + 1) & 1))
			return false;
	return true;
}
int count(int state){
	int res = 0;
	for(int i = 0;i < n;i ++)
		if(state >> i & 1) res ++;
	return res; 
}
int main(){
	cin >> n >> m;
	for(int i = 0;i < 1 << n;i ++)
		if(check(i)){
			state.push_back(i);
			cnt[i] = count(i);
		}
	f[0][0][0] = 1;
	for(int i = 1;i <= n + 1;i ++)
		for(int j = 0;j <= m;j ++)
			for(int k = 0;k < state.size();k ++)
				for(int u = 0;u < state.size();u ++){
					int a = state[k],b = state[u];
					if(!check(a | b)) continue;
					if(a & b) continue;
					int e = cnt[a];
					if(j >= e)
						f[i][j][a] += f[i - 1][j - e][b];
				}
	cout << f[n + 1][m][0];
	return 0;
}

题目3:洛谷:炮兵阵地

此题地形要求类似于第一题,计数要求类似于第二题,不同的是该题状态不只受自身、上层状态影响,还与上上层状态有关。

这时我们可以枚举这三层所有合法状态,对其进行判定,具体可见代码。

要注意的是该题需要三维数组,对空间要求较大。由于动态规划没有后效性,一层状态的f数组只与上层状态的f数组有关,我们可以用滚动数组进行优化。

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N = 110,M = 1 << 10;
int w[N];
vector<int> state;
int cnt[M];
int n,m;
int f[2][M][M];
bool check(int state){
	for(int i = 0;i < m;i ++)
		if(state >> i & 1 && ((state >> i + 1) | (state >> i + 2)) & 1)
			return false;
	return true;
}
int count(int state){
	int res = 0;
	for(int i = 0;i < m;i ++)
		if(state >> i & 1) res ++;
	return res;
}
int main(){
	cin >> n >> m;
	for(int i = 1;i <= n;i ++)
		for(int j = m;j >= 1;j --){
			char a;
			cin >> a;
			w[i] += (a == 'H') << (j - 1);
		}
	for(int i = 0;i < 1 << m;i ++)
		if(check(i)){
			state.push_back(i);
			cnt[i] = count(i);
		}
	for(int i = 1;i <= n;i ++)
		for(int j = 0;j < state.size();j ++)
			for(int k = 0;k < state.size();k ++)
				for(int u = 0;u < state.size();u ++){
					int a = state[j],b = state[k],c = state[u];
					if(a & b | a & c | b & c) continue;
					if(a & w[i] | b & w[i - 1] | c & w[i - 2]) continue;
					f[i & 1][k][j] = max(f[i & 1][k][j],f[i - 1 & 1][u][k] + cnt[a]);
				}
	int ans = 0;
	for(int i = 0;i < state.size();i ++)
		for(int j = 0;j < state.size();j ++)
			ans = max(f[n & 1][i][j],ans);
	cout << ans;
	return 0;
}

2、一维状压dp

题目1:洛谷:Cows in a Skyscraper G

我们可以设计一个n维的二进制数,1表示奶牛进去电梯了,0表示奶牛尚未进电梯,再用g数组存储该状态剩余空间,每次枚举奶牛,如果奶牛体重加上g[加奶牛之前的状态]小于限重,就可以加进去,否则需要多开一个电梯。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 20,M = 1 << 18;
int w[N];
int n,m;
int f[M],g[M];
int main(){
	cin >> n >> m;
	for(int i = 1;i <= n;i ++)
		cin >> w[i];
	memset(f,63,sizeof f);
	f[0] = 1;
	g[0] = m;
	for(int i = 0;i < 1 << n;i ++){
		for(int j = 1;j <= n;j ++){
			if(i & 1 << (j - 1)) continue;
			if(g[i] >= w[j] && f[i | 1 << (j - 1)] >= f[i]){
				f[i | 1 << (j - 1)] = f[i];
				g[i | 1 << (j - 1)] = max(g[i | 1 << (j - 1)],g[i] - w[j]);
			}
			else if(g[i] < w[j] && f[i | 1 << (j - 1)] >= f[i] + 1){
				f[i | 1 << (j - 1)] = f[i] + 1;
				g[i | 1 << (j - 1)] = max(g[i | 1 << (j - 1)],m - w[j]);
			}
		}
	}
	cout << f[(1 << n) - 1];
	return 0;
}

题目2:洛谷:yyy loves Maths VII

此题类似于上一题,初始状态全为0(即一步都没走),之后走过哪一步就将那一步对应的一位变成1,每次走时都要判断加起来的步数是否为厄运数字。

此题最难的点不在于思维,而在于优化,卡常数卡得很紧。

在我进行了无数次tle后看了题解,学到了很多优化时间的方法:将函数放在define中(如代码中的lowbit)、for循环中将int前加上register、将模运算改为减法运算。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#define lowbit(x) ((x) & (-x))
using namespace std;
const int N = 26,M = 1 << 24,K = 1000000007;
int f[M],g[M],a[3];
int n,m;
int main(){
	cin >> n;
	for(int i = 1;i <= n;i ++) cin >> g[1 << (i - 1)];
	cin >> m;
	for(int i = 0;i < m;i ++)
		cin >> a[i];
	f[0] = 1;
	for(register int i = 1;i < 1 << n;i ++){
		g[i] = g[i ^ lowbit(i)] + g[lowbit(i)];
		if(a[0] == g[i] || a[1] == g[i]) continue;
		for(register int j = i,k = lowbit(j);j;j ^= k,k = lowbit(j)){
			f[i] += f[i ^ k];
			if(f[i] >= K) f[i] -= K;
		}
	}
	cout << f[(1 << n) - 1];
	return 0;
}

3、二维函数状压dp

题目:洛谷:愤怒的小鸟

该题基于一元二次方程(抛物线),由于该抛物线一定会经过原点,故该方程为 y = ax + b 只需再找到两个点(这两个点横坐标不能相同)就可确定一个方程。我们可以二次枚举所有的点(猪头),将所有横坐标不同的一对点对应的方程存下来,并存储每个方程对应的消灭的猪头状态(如一共有五头猪,它消灭了第1、第3、第4头,状态表示为 01101

之后就是经典的状压问题了。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#define x first
#define y second
using namespace std;
typedef pair<double,double> PDD;
const int N = 20,M = 1 << 18;
const double eps = 1e-8;
int n,m;
PDD q[N];
int path[N][N];
int f[M];
int cmp(double x,double y){
	if(fabs(x - y) < eps) return 0;
	if(x < y) return -1;
	return 1;
}
int main(){
	int t;
	cin >> t;
	while(t --){
		cin >> n >> m;
		for(int i = 0;i < n;i ++)
			cin >> q[i].x >> q[i].y;
		memset(path,0,sizeof path);
		for(int i = 0;i < n;i ++){
			path[i][i] = 1 << i;
			for(int j = 0;j < n;j ++){
				double x1 = q[i].x,y1 = q[i].y;
				double x2 = q[j].x,y2 = q[j].y;
				if(!cmp(x1,x2)) continue;
				double a = (y1 / x1 - y2 / x2) / (x1 - x2);
				double b = y1 / x1 - a * x1;
				if(cmp(a,0.0) >= 0) continue;
				int state = 0;
				for(int k = 0;k < n;k ++){
					double x = q[k].x,y = q[k].y;
					if(!cmp(a * x * x + b * x,y)) state += 1 << k;
					path[i][j] = state;
				}
			}
		}
		memset(f,0x3f,sizeof f);
		f[0] = 0;
		for(int i = 0;i < 1 << n;i ++){
			int x = 0;
			for(int j = 0;j < n;j ++)
				if(!(i >> j & 1)){
					x = j;
					break;
				}
			for(int j = 0;j < n;j ++)
				f[i | path[x][j]] = min(f[i | path[x][j]],f[i] + 1);
		}
		cout << f[(1 << n) - 1] << endl;
	}
	return 0;
}

4、后效性问题

题目:洛谷:关灯问题II
记录此题的原因是此题可以用状态压缩AC,但这是因为数据太水,没有卡掉后效性问题。具体可见题解区与讨论区。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值