深入学习C++:什么是状压DP

深入学习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 个城市和两两之间的距离,求从起点出发,访问所有城市恰好一次并返回起点的最短路径。

步骤解析
  1. 状态定义dp[mask][u] 表示 “已访问的城市集合为 mask,当前在城市 u 时的最短距离”。

  2. 转移方程
    对于每个状态 (mask, u),枚举未访问的城市 v,更新新状态 (mask | (1<<v), v)
    dp[mask | (1<<v)][v] = min(dp[mask | (1<<v)][v], dp[mask][u] + dist[u][v])

  3. 初始化dp[1<<0][0] = 0(从城市 0 出发,初始只访问城市 0)。

  4. 结果:遍历所有城市 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;
}

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值