深入学习C++:什么是状压DP
一. 基础概念
状压 DP是一种利用二进制状态来表示集合状态的动态规划方法,特别适合解决状态数量有限且可枚举的问题(通常元素数量不超过 20-25,因为 2^25 约为 3300 万,超过这个范围会导致状态爆炸)。
核心思想:
1. 二进制状态表示:用一个整数的二进制位表示集合中元素的选择状态。
例如,mask = 0b1011(二进制)表示第 0、1、3 个元素被选中(从右往左数,低位为 0)。
2. 状态定义:dp[mask] 或 dp[mask][u] 表示在状态 mask 下的最优解(如最小代价、最大价值、方案数等)。
3. 状态转移:通过枚举当前状态的子集或可扩展的元素,从已知状态推导出新状态。
状压 DP 的优缺点
优点:能高效解决小规模集合问题,状态表示直观,适合枚举所有可能的集合状态。
缺点:时间和空间复杂度随元素数量呈指数增长(O(2^n)),仅适用于 n ≤ 20-25 的场景。
典型应用场景:
1. 旅行商问题(TSP):访问所有城市的最短路径。
2. 集合覆盖问题:选择最少子集覆盖所有元素。
3. 排列组合问题:如 “用给定元素组成特定排列的方案数”。
4. 网格覆盖问题:如 “用 1x2 骨牌覆盖网格的方法数”。
关键技术点:
1. 二进制操作:
(1). 检查第 i 位是否为 1:mask & (1 << i)
(2). 置第 i 位为 1:mask | (1 << i)
(3). 置第 i 位为 0:mask & ~(1 << i)
(4). 枚举 mask 的所有子集:submask = (submask - 1) & mask(经典子集枚举法)。
2. 状态压缩:用一个整数表示集合状态,大幅减少存储空间(相比用布尔数组表示集合)。
3. 时间复杂度:通常为 O(2^n * n) 或 O(2^n * n^2),其中 n 是元素数量,适合 n ≤ 20 的问题。
二. 示例
旅行商问题(TSP)
问题:给定 n 个城市和两两之间的距离,求从起点出发,访问所有城市恰好一次并返回起点的最短路径。
步骤解析:
-
状态定义:
dp[mask][u]表示 “已访问的城市集合为mask,当前在城市u时的最短距离”。 -
转移方程:
对于每个状态(mask, u),枚举未访问的城市v,更新新状态(mask | (1<<v), v):
dp[mask | (1<<v)][v] = min(dp[mask | (1<<v)][v], dp[mask][u] + dist[u][v]) -
初始化:
dp[1<<0][0] = 0(从城市 0 出发,初始只访问城市 0)。 -
结果:遍历所有城市
u,求dp[(1<<n)-1][u] + dist[u][0]的最小值(返回起点)。
代码如下:
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;
int tsp(vector<vector<int>>& dist) {
int n = dist.size();
if (n == 0) return 0;
const int INF = INT_MAX / 2; // 避免加法溢出
int full_mask = (1 << n) - 1; // 所有城市都访问过的状态(二进制全1)
// dp[mask][u]:状态为mask时,当前在城市u的最短距离
vector<vector<int>> dp(1 << n, vector<int>(n, INF));
dp[1 << 0][0] = 0; // 从城市0出发
// 枚举所有状态
for (int mask = 0; mask < (1 << n); ++mask) {
// 枚举当前所在城市u
for (int u = 0; u < n; ++u) {
if (!(mask & (1 << u))) continue; // u不在当前状态中,跳过
// 枚举下一步要去的城市v
for (int v = 0; v < n; ++v) {
if (mask & (1 << v)) continue; // v已访问,跳过
int new_mask = mask | (1 << v); // 新状态:加入v
dp[new_mask][v] = min(dp[new_mask][v], dp[mask][u] + dist[u][v]);
}
}
}
// 从最后一个城市返回起点0
int result = INF;
for (int u = 1; u < n; ++u) {
result = min(result, dp[full_mask][u] + dist[u][0]);
}
return result;
}
1万+

被折叠的 条评论
为什么被折叠?



