首页 > 其他分享 >状态压缩 DP - 知识点梳理

状态压缩 DP - 知识点梳理

时间:2023-07-22 09:11:51浏览次数:39  
标签:知识点 状态 int 压缩 状压 long 梳理 DP

状态压缩 DP,或状压 DP,是对状态的一种优化。相比于普通 DP,通过将高维状态压缩成一个数,减少了维度,并使维度更易于存储与维护。同时这样与 bitset 一样利用了计算机在 \(O(1)\) 内处理位运算的能力,大幅度优化了时间复杂度。
一般当题目中的状态由多个 \(0\) / \(1\) 组成,数量不一定,且需要对状态进行操作时,可以使用状压 DP 优化状态。

实现

状压 DP 的核心在于状态压缩。前者是一种算法,而后者是一种思想,且前者基于后者。
状态压缩的思想:将一个由 \(0\) / \(1\) 构成的状态压缩为一个二进制数并用其表示。这样便于存储、传递、枚举与维护,并充分利用计算机 \(O(1)\) 处理位运算的能力。在 DP 问题中,并不是所有状态都是有效的,因此为了节省空间,减小常数,应尽量减少无效的状态,而状态压缩的思想恰好满足这个要求,因为它无效的状态都可以被去除掉,这就是它被广泛应用于 DP,并形成状压 DP 的根本原因。
例题:Luogu P1896 [SCOI2005] 互不侵犯
考虑怎么设状态。发现假设当前枚举到第 \(i\) 行第 \(j\) 列,当前状态与它左边的方格和上面的方格都有关系,也就是说一个状态要牵扯到它本身行与它的上一行,极难维护,于是考虑进行压缩。
发现 \(N\) 的范围很小,我们其实不用一个一个方格枚举,而可以一行一行枚举,只要当前行与上一行整体没有冲突,就可以累计答案了。这样每行都只与上一行有关联,只需要在状态中维护当前行的状态就可以了。
每一行有 \(N\) 个方格,也就是说状态一共有 \(N\) 维,这样就不方便维护了。
因为每维都是 \(0\) / \(1\),所以我们可以把一行的状态压缩为一个数,这就是状态压缩。这样,我们就可以仅用两维表示出所有状态:\(i\) 表示当前行标,\(j\) 表示当前行的状态。
现在考虑如何进行转移。状态 \((i - 1, j)\) 能转移到 \((i, k)\) 当且仅当 \(j\),\(k\) 合法且 \(j\) 可以放在 \(k\) 的上面。
一个状态合法,代表其中不存在两个相邻位为 \(1\)。两个状态能叠,代表不存在同位上两个状态都为 \(1\)。我们需要对这两种情况进行判断。对于这道题来说,暴力枚举每一位已经可以 AC 了。
但其实对状态进行压缩之后,自然地产生了一种新的对于状态的处理方式:位运算。
我们可以把过程叙述转化为公式。
一个状态合法,当且仅当它满足公式 i & (i << 1) == 0。两个状态能叠,当且仅当 i & j == 0
这样状态处理一般会快得多。
这样就做完了。
上代码!

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

unsigned long long N, K, dp[10][100][512];
unsigned long long bitcount(unsigned long long c){
	if (c == 0) return 0;
	else return (c&1)+bitcount(c>>1);
}
int main(){
	cin >> N >> K;
	dp[0][0][0] = 1;
	for (int i = 1;i <= N;i++)
		for (int j = 0;j <= K;j++){
			for (int k = 0;k < (1<<N);k++){
				if ( (k & (k<<1)) > 0 || bitcount(k) > j) continue;
				for (int k0 = 0;k0 < (1<<N);k0++){
					if ((k0 & (k0<<1)) > 0 || (k&k0) > 0 || ((k<<1)&k0) > 0 || ((k>>1)&k0) > 0) continue;
					dp[i][j][k] += dp[i-1][j-bitcount(k)][k0];
				}
			}
		}
	unsigned long long sum = 0;
	for (int i = 0;i < (1<<N);i++) sum += dp[N][K][i];
	cout << sum;
}

这道题就体现了状压 DP 的两个作用:优化状态,优化处理。

常见应用

覆盖问题

这是最简单的一类应用,求在矩阵上以某种方式覆盖的方案数量。
我们可以使用状态压缩维护最近几行的状态,然后按题意转移即可。
相对于普通 DP,这样做的优点是可以一行一行处理,状态较为简洁,而且可以通过位运算实现对行的各种操作。
常见操作:

操作 表达式
检测两行重叠元素 \(x\and y\)
检测两行不同元素 \(x\or y\)
将行向左/右平移 \(x << k\) / \(x >> k\)

例题:Luogu P1879 [USACO06NOV] Corn Fields G
先按照这种题的套路(?)设出状态:\(f_{i, j}\) 表示第 \(i\) 行状态为 \(j\) 的方案数。
首先每一行必须合法,一个条件是该行本身不能有相邻元素。即将这一行位移后应不与原行有共同点。于是第一个条件:j & (j << 1) == 0
同时该行也不能再贫瘠的草地上种东西,即它与贫瘠草地在本行的出现不得出现共同点。我们将贫瘠草地的状态预处理并设为 \(b[i]\),则 b[i] & i == 0
然后这行与上一行也要组成合法状态。我们需要枚举上一行的状态,同理得 j & k == 0
这就是所有的条件。
上代码!

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

int n, m, state[105], f[105][MAXSTATE+5];
int main(){
	cin >> m >> n;
	for (int i = 1;i <= m;i++){
		int cur = 0, tmp;
		for (int j = 1;j <= n;j++){
			cin >> tmp;
			cur <<= 1;
			cur |= tmp;
		}
		state[i] = cur;
	}
	f[0][0] = 1;
	for (int i = 1;i <= m;i++){
		for (int j = 0;j <= pow(2, n)-1;j++){
			if ((j|state[i]) > state[i]) continue;
			if ((j&(j<<1)) > 0 || (j&(j>>1)) > 0) continue;
			for (int k = 0;k <= pow(2, n)-1;k++){
				if ((k|state[i-1]) > state[i-1]) continue;
				if ((k&(k<<1)) > 0 || (k&(k>>1)) > 0 || (j&k) > 0) continue;
				f[i][j] += f[i-1][k]; 
			}
		}
	}
	int s = 0;
	for (int i = 0;i <= pow(2, n)-1;i++){
		s += f[m][i];
		s %= 100000000;
	}
	cout << s % 100000000;
}

可见这种类型的题极其套路化,关键在于如何将覆盖信息转化为位运算。

图上不相交路径问题

有时我们要统计图上不相交的路径条数,这时当点数较少时我们可以使用状压 DP,使用状态记录那些点被访问过,再进行 DP。这种问题尤其要注意时空限制,毕竟在指数级别复杂度时很小的数据也能导致超时。
例题:CF11D. A Simple Task
非常值得做的一道题,这个模板会在很多地方遇到
这道题有很多很好想的错误做法,这里只讲正确的做法。
每个点只经过一次的环,数据范围 \(n\le 19\),自然想到状压 DP。稍作思考后发现当前状态与访问过的点和当前的点有关,于是设出状态 \(f_{i, j}\) 代表访问过的点的状态为 \(i\),现在在 \(j\) 的路径条数。
然后考虑去重。我们发现每个环被访问次数相当于环上点的个数,于是我们使用代表元法,为每个环指派一个代表元。因为我们是在点上 DP,直接选择其最小节点作为代表元即可。
这里又体现了状压 DP 的优势。其最小的节点相当于其状态的最低位,在 \(O(1)\) 时间即可算出,为 log2(i&-i)
然后最后再去掉长度为 \(2\),即 \(a\rarr b\rarr a\) 类的环即可。这样的环一共有 \(m\) 个。
上代码!

#include<bits/stdc++.h>
#define int long long
using namespace std;
 
const int MAXN = 19, MAXM = 405;
struct edge {
	int u, v;
}e[2 * MAXM];
int n, m, f[1 << MAXN][MAXN + 1], lg2[1 << MAXN];
vector<int> states[MAXN];
int pop (int x) {
	return x ? (x & 1) + pop (x >> 1) : 0;
}
int cor (int x) {
	return 1 << (x - 1);
}
signed main () {
	cin >> n >> m;
	for (int i = 1;i < (1 << n);i++) lg2[i] = lg2[i / 2] + 1;
	for (int i = 1;i <= m;i++) {
		cin >> e[2 * i - 1].u >> e[2 * i - 1].v;
		e[2 * i].v = e[2 * i - 1].u;
		e[2 * i].u = e[2 * i - 1].v;		
	}
	for (int i = 0;i < (1 << n);i++) {
		states[pop(i)].push_back(i);
	}
	int ans = 0;
	for (int i = 1;i <= n;i++) f[cor(i)][i] = 1;
	for (int len = 1;len <= n;len++){
		for (int i: states[len]) {
			for (int j = 1;j <= 2 * m;j++) {
				if (!(i & cor (e[j].u))) continue;
				if (e[j].v < lg2[i&-i]) continue;
				if (i & cor(e[j].v)) {
					if (e[j].v == lg2[i&-i]) ans += f[i][e[j].u];
					continue; 
				}
				f[i | cor (e[j].v)][e[j].v] += f[i][e[j].u];
			}
		}
	}
	cout << (ans - m) / 2 << endl;
	return 0;
}

以上只是状压 DP 最经典的几种应用,实际上其他的应用还要比这多得多,重在理解状态压缩的思想。

注意事项

  1. 状态压缩的题目特点是数据范围比较小,且关心一些元素的存在状态。解决时需要特别注意数据范围。一个指数级别的算法很容易被卡掉。
  2. 要尽量利用位运算的优势,减少常数。(通常是因为这类的题需要卡掉很多暴力做法,所以时空范围较为严格)
  3. 要充分理解状态压缩的思想。有可能这种思想会运用于非 DP 的题,这时它依然有以上作用。
  4. 在进行位运算是要分清楚位标和位值的区别,使用正确的值进行运算。

标签:知识点,状态,int,压缩,状压,long,梳理,DP
From: https://www.cnblogs.com/mindeveloped/p/monke-dp.html

相关文章

  • m基于扩频解扩+LDPC编译码的通信链路matlab误码率仿真,调制对比QPSK,16QAM,64QAM,扩频
    1.算法仿真效果matlab2022a仿真结果如下:     2.算法涉及理论知识概要      在现代通信系统中,扩频技术被广泛应用于数字通信链路中。扩频技术通过将要传输的信息序列与一个宽带的伪随机码序列进行卷积,将原始信号转换成一个具有更大带宽的扩频信号。在接收端......
  • K8S初始化报错:CRI v1 runtime API is not implemented for endpoint \"unix:///var/r
    报错具体内容:[preflight]Somefatalerrorsoccurred:[ERRORCRI]:containerruntimeisnotrunning:output:time="2023-07-21T09:20:07Z"level=fatalmsg="validateserviceconnection:CRIv1runtimeAPIisnotimplementedforendpoint\"un......
  • Hibernate初始化时在OneToOneSecondPass类中出现NullPointerException
    启动项目 Hibernate随即报错Causedby:java.lang.NullPointerException   atorg.hibernate.cfg.OneToOneSecondPass.doSecondPass(OneToOneSecondPass.java:135)  原因: 主类方,无外键方@OneToOne(mappedBy="carveEReviewproject",targetEntity=CarveEReviewcomment.cla......
  • 多个 Lector621 组网读取 PCB 板上的 DPM 码
    第一部分:现场需求/问题描述 客户购买了5套SICKLector621: 1. 客户要求视野范围500mm; 2. 安装高度:200mm以下; 3. DPM码:4mm*4mm(点数:16*16),分辨率0.25;第二部分:现场工作内容 1.产品功能和参数设置: a. 安装和电气连接:  安装图 1.单台读码器安装......
  • Scrapy 部署错误:subprocess.CalledProcessError 以及解决方案
    最近在使用Scrapy和Scrapyd时,我遇到了一个关于subprocess.CalledProcessError的问题。在这篇博文中,我将描述这个错误、找出的原因以及最后的解决方案。错误描述在使用scrapyd-deploy命令部署我的Scrapy项目时,我遇到了如下的错误:subprocess.CalledProcessError:Comma......
  • 【网络流,dp】Gym102220A Apple Business
    ProblemLink有一棵\(n\)个点的完全二叉树(点\(i\)的父亲是\(\lfloori/2\rfloor\)),第\(i\)个点有\(a_i\)个苹果。现在有\(m\)个订单,每个订单只接受\(u_i\)到\(v_i\)路径上的苹果,保证\(u_i\)是\(v_i\)的父亲,并且最多只接受\(c_i\)个苹果,单价为\(w_i\)。你可......
  • pdf自动化框架基础指南十三大类知识点
    少说话,直接上图,先给大家看一个大概的本自动化框架基础指,面向零基础、有一定自动化测试经验但缺乏系统的基础知识的人员目的是提供一个相对系统的自动化框架知识经验的分享,本文档不保证其先进性,精确性,欢迎拍砖打我下一步计划是准备耗费2-3个月业余时间,对IEEE中关键字驱动框架相关文......
  • 网络编程 p5 UDP编程
    UDP网络通信编程基本介绍类DatagramSocket和DatagramPacket实现了基于UDP协议网络程序。UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。DatagramPacket对象封装了UDP数据报,在数据报中包含了发......
  • 20230719-动态规划DP
    20230719数位DPP4127[AHOI2009]同类分布题目描述传送门求出[a,b]中各位数字之和能整除原数的数的个数\(a,b≤1e18\)Solution对于这种求是否能整除的题我们只有在最后才能得到答案这道题很明显是数位DP考虑用记忆化搜索来实现对于每一位我们需要维护前面的数字之......
  • acwing选数异或 dp
    题目链接:https://www.acwing.com/problem/content/description/4648/题解链接[转载]:https://www.acwing.com/solution/content/137064/1#include<iostream>2#include<algorithm>3#include<vector>4#include<string>5#include<queue>......