个人学习笔记平台,欢迎大家交流

红叶~

Be humble, communicate clearly, and respect others.

状态压缩DP

状态压缩DP

对于某些动态规划问题,可以用深搜来枚举状态,但是那样的话时间复杂度就太高了。对于此类问题我们采用二进制表示状态,用1和0来表示某位置不同的状态。
1、对于状压DP问题,我们一般取一个初始状态。确定状态数组的含义。
2、明确相邻状态的转移,一般我们可以记录某个状态可以转移的相邻状态(打表)。
3、然后从第一行(列)枚举到最后行(列)[视具体情况而定],根据符合条件的相邻之间的状态转移,由前面推出后面(往往最后才是最终答案)。

下面是做得几道题:

AcWing327玉米田

AcWing291.蒙德里安的梦想

AcWing 1064. 小国王

十二届蓝桥杯回路计数

【思路】
这道题和最短Hamilton路径差不多,只是状态要求的集合属性不同。
使用状态压缩DP,使用二进制状态转移

  • 状态表示:f[i][j]表示经过路径状态 i 且从1走到j点的方案数
  • 我们将点1 ~ 21映射到0 ~ 20,遍历使用二进制压缩状态
  • 初始时f[1][0] = 1在起点 0 也为 1 种方案
  • 最后我们求出所有状态全1且当前点除1以外的点,表示从起点开始所有点都经过了一次,因为起点\(1\)和别的点都互质,所以都互通,那么下一次就可以直接从其它点回到起点了。
    注意
    \(1 << m = 2 ^ m\)
    \((1 << m) - 1\) 表示 m\(1\)
    【代码】
#include <iostream>
using namespace std;
const int N = 21, M = 1 << 21;
typedef long long LL;
LL f[M][N]; // f[i][j]从0走到j,且路径状态为i的方案数
bool g[N][N];

int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}

int main() {
	for (int i = 1; i <= 21; i++)
		for (int j = 1; j <= 21; j++) {
			if (gcd(i, j) == 1)
				g[i - 1][j - 1] = true;
		}
	f[1][0] = 1;
	for (int state = 1; state < M; state++)
                if(state & 1) // 要包括起点,但是这里也可不写,因为不包含起点的状态结果是0,这里是求和没有影响,如果是取最小值,就必须加上这句
		for (int j = 0; j <= 20; j++)
			if (state >> j & 1) {
				for (int k = 0; k <= 20; k++) {
					if (state ^ (1 << j) >> k & 1 && g[k][j])
						f[state][j] += f[state ^ (1 << j)][k];
				}
			}

	LL ans = 0; // M-1为全1所有点都经过了一次,1和所有数互质,所以其它点都可回到1(原点0)
	for (int i = 1; i <= 20; i++)
		ans += f[M - 1][i];
	cout << ans << endl;
	return 0;
}

AcWing 91. 最短Hamilton路径

AcWing 3494. 国际象棋(蓝桥杯题)

posted @ 2022-02-18 21:47  红叶~  阅读(48)  评论(0编辑  收藏  举报