状态压缩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;
}