【动态规划】状态压缩DP(状压dp)

还在更新ing

一、引入

在动态规划状态设计中,若状态是一个集合,例如 \(S=\) { \(1,0,1,1,0\) } ,则表示第 \(1、2、4\)节点被选中(从右往左对应 \(0 \sim 4\) 号节点)。若集合的大小不超过 \(N\) ,则集合中的每个元素都是小于 \(K\) 的正整数,可以把这个集合看作一个 \(N\)\(K\) 进制数,以一个 \([0,K^N-1]\)十进制整数作为 DP 状态。可以将 \(S=\) { \(1,0,1,1,0\) } 看作一个 \(5\)二进制数 \(10110\) ,其对应的十进制数\(21\)
这种将集合作为整数记录状态的一类算法叫作状态压缩 DP 。在状态压缩 DP 中,状态的设计直接决定了程序的效率或者代码长短。我们需要根据问题分析本质,才能更好地找出恰当的状态表示状态转移方程边界条件

二、二进制 & 位运算

尽管用了一个十进制数据储存二进制状态,当因为操作系统是二进制的,所以在编译器中也可采用位运算解决这个问题。

1、基本位操作运算符

在状态压缩 DP 中广泛运用位运算操作,常见的位运算如下 :

A B ~A(差) \(A \& B\)(与) \(A | B\)(或) A ^ B(异或)
0 0 1 0 0 0
0 1 1 0 1 1
1 0 0 0 1 1
1 1 0 1 1 0

如果 \(A,B\) 为整数,则可看成长度为 \(32\) 位的两个二进制数各个位分别做上面操作。
例如:
\(A=10 = (1010)_B,B=12 = (1100)_B\) 时,

\(A \& B = 8 = (1000)_B\)
\(A | B = 14 = (1110)_B\)
\(A\) ^ \(B = 6 (0110)_B\)
~ \(A = -11 (1...10101)_B\)

另外,还有两个位移操作运算符左移(<<)右移(>>)
如:A<<B,表示 \(A\) 的所有位都向左移动 \(B\) 个,后面 \(B\) 位用 \(0\) 补充。
注意,对二进制数,位数高低为从右到左依次为 \(0\) 位、\(1\) 位、\(2\)\(\ldots\)
所以我们可以认为 A<<B 等价于 \(A \times 2B\)A>>B 等价于 \(\frac {A}{2B}\)

2,常见的操作有:

去掉最后一位 ( \(101101 \to 10110\) ) x >> 1
在最后加一个 \(0\) ( \(101101 \to 1011010\) ) x << 1
在最后加一个 \(1\) ( \(101101 \to 1011011\) ) x << 1 | 1
把最后一位变成 \(1\) ( \(101100 \to 101101\) ) x | 1
把最后一位变成 \(0\) ( \(101101 \to 101100\) ) x | 1 - 1
最后一位取反 ( \(101101 \to 101100\) ) x ^ 1
把右数第 \(k\) 位变成 \(1\) ( \(101001 \to 101101,k=3\) ) x | (1 << (k - 1))
把右数第 \(k\) 位变成 \(0\) ( \(101101 \to 101001,k=3\)) x & not (1 << (k - 1))
右数第 \(k\) 位取反 ( \(101001 \to 101101,k=3\) ) x ^ (1 << (k - 1))
取末三位 ( \(1101101 \to 101\) ) x & 7
取末 \(k\) 位 ( \(1101101 \to 1101,k=5\) ) x & (1 << k - 1)
取右数第 \(k\) 位 ( \(1101101 \to 1,k=4\) ) x >> (k - 1) & 1
把末 \(k\) 位变成 \(1\) ( \(101001 \to 101111,k=4\) ) x | (1 << k-1)
\(k\) 位取反 ( \(101001 \to 100110,k=4\) ) x ^ (1 << k-1)
把右边连续的 \(1\) 变成 \(0\) ( \(100101111 \to 100100000\) ) x & (x + 1)
把右起第一个 \(0\) 变成 \(1\) ( \(100101111 \to 100111111\) ) x | (x + 1)
把右边连续的 \(0\) 变成 \(1\) ( \(11011000 \to 11011111\) ) x | (x - 1)
取右边连续的 \(1\) ( \(100101111 \to 1111\) ) (x ^ (x + 1)) >> 1

3、表示集合(状态压缩)

有时我们用 \(32\) 位的整型数的各个位表示一个最多 \(32\) 个元素的集合,第 \(i\) 位为 \(1\) 表示第 \(i\) 个元素在集合中,为 \(0\) 则表示不在集合中。
常见的操作有:
并集操作 A | B
交集操作 A & B
集合的差 A & ~ B
补集 -1 ^ A\(-1\) 为二进制数 \(111...1\))
加入第 \(i\) 个元素 A = A | (1 << (i - 1))
删除第 \(i\) 个元素 A = A & ~ (1 << (i - 1))
判断第 \(i\) 个元素 (A & (1 << (i - 1))) != 0

4、lowbit

求最低位的 \(1\) 的位置,即著名的 lowbit 问题。

lowbit(int x) {
	return x&(-x);
}

三、实现流程

状态压缩 DP 大可分为两类 :

  • 棋盘式(基于连通性)DP
  • 集合式 DP

状态压缩dp三部曲:

  • 考虑如何状态压缩
  • 确定状态表示和状态转移方
  • 根据实际问题确定筛选条件

例题引入

著名的旅行商问题( Traveling Salesman Problem,TSP )指一个旅行商从一个城市出发,经过每一个城市一次且只有一次回到原来的地方,要求经过的距离最短。
在这里插入图片描述
TSP 问题是一个 NP 难题,目前没有多项式时间的高效算法。若采用搜索+剪枝,则该算法的时间复杂度为 \(O(n!)\) ,数据量大时,这种方法无法解决,可以尝试采用动态规划解决。
假设已访问的节点集合为 \(S\) (将源点 \(0\) 当作未被访问的节点,因为从 \(0\) 出发,所以要回到 \(0\) ),当前所在的节点为 \(u\)

I. 状态表示

\(f_{S,u}\) 表示已经访问的节点集合为 \(S\) ,从 \(u\) 出发走完所有剩余节点回到源点的最短距离。

II. 状态转移方程

若当前 \(u\) 的邻接节点 \(v\) 未被访问,则 \(f_{S,u}\) 由两部分组成,第 \(1\) 部分是 \(u\)\(v\) 的边值,第 \(2\) 部分是从 \(v\) 出发走完所有剩余节点再回到源点的最短距离。访问完 \(v\) 之后,已访问的节点集合变为 \(S \cap\) { \(v\) } ,若 \(u\) 有多个未被访问的邻接点 \(v\) ,则取最小值。

\[f_{S,u}=\min(f_{S \cap v,v}a+w_{u \to v} | v \notin S) \]

III. 临界条件

\(f_{(1<<n)-1,0}=0\) ,表示若所有节点都被访问,则此时已经没有剩余节点,从 \(0\) 节点出发走完所有剩余节点回到源点的最短距离为 \(0\)

Code

void Traveling() {    //计算f[S][u]
	f[(1<<n)-1][0]=0;     //1<<n一定要加括号(别问我为什么)
	for(int S=(1<<n)-2;S>=0;S--) {
		for(int u=0;u<n;u++) {
			for(int v=0;v<n;v++) {
				if((u!=0&&!(S>>u&1))||w[u][v]==inf) continue;   //可以加约束条件,不加太多状态
				if(!(S>>v&1)&&f[S][u]>f[(S|1<<v)][v]+w[u][v]) {
					f[S][u]=f[(S|1<<v)][v]+w[u][v];
					path[S][u]=v;     //记录后继节点
				} 
			} 
		}
	}
}

训练 1 :[USACO06NOV] Corn Fields G

源自 洛谷 P1879 [USACO06NOV] Corn Fields G

题目描述

农场主 \(\rm John\) 新买了一块长方形的新牧场,这块牧场被划分成 \(M\)\(N\)\((1 \le M \le 12; 1 \le N \le 12)\),每一格都是一块正方形的土地。 \(\rm John\) 打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是 \(\rm John\) 不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

\(\rm John\) 想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

输入格式

第一行:两个整数 \(M\)\(N\),用空格隔开。

\(2\) 到第 \(M+1\) 行:每行包含 \(N\) 个用空格隔开的整数,描述了每块土地的状态。第 \(i+1\) 行描述了第 \(i\) 行的土地,所有整数均为 \(0\)\(1\) ,是 \(1\) 的话,表示这块土地足够肥沃,\(0\) 则表示这块土地不适合种草。

输出格式

一个整数,即牧场分配总方案数除以 \(100,000,000\) 的余数。

样例 #1

样例输入 #1

2 3
1 1 1
0 1 0

样例输出 #1

9

题解

I. 分析题意

本题求在值为 \(1\) 的土地上种上植草地,每个草地的上下左右相邻的土地上不能有草地,求所有的种植方案数。

II. 算法设计

Code

posted @ 2023-11-06 14:02  Fireworks_Rise  阅读(287)  评论(0)    收藏  举报