牛客题解 | 04.08_状压dp

状压 DP

问题描述

状态压缩动态规划是指用二进制数表示状态的动态规划。
常见的有旅行商问题、集合划分等。
通常用一个整数的二进制位表示元素的选择状态。

旅行商问题(TSP)

算法思想

  1. 定义dp[state][i]表示已访问城市集合为state且当前在城市i的最小代价
  2. 枚举下一个要访问的城市j
  3. 状态转移方程:dp[state | (1<<j)][j] = min(dp[state][i] + cost[i][j])
  4. 最终答案为dp[(1<<n)-1][end]

代码实现

int tsp(vector<vector<int>>& cost) {
    int n = cost.size();
    vector<vector<int>> dp(1 << n, vector<int>(n, INT_MAX));
    dp[1][0] = 0;  // 初始在城市0
    
    // 枚举状态
    for (int state = 1; state < (1 << n); state++) {
        // 枚举当前城市
        for (int i = 0; i < n; i++) {
            if (!(state & (1 << i))) continue;
            // 枚举下一个城市
            for (int j = 0; j < n; j++) {
                if (state & (1 << j)) continue;
                int nextState = state | (1 << j);
                dp[nextState][j] = min(dp[nextState][j], 
                                     dp[state][i] + cost[i][j]);
            }
        }
    }
    
    return dp[(1 << n) - 1][n-1];
}
public int tsp(int[][] cost) {
    int n = cost.length;
    int[][] dp = new int[1 << n][n];
    for (int[] row : dp) {
        Arrays.fill(row, Integer.MAX_VALUE);
    }
    dp[1][0] = 0;  // 初始在城市0
    
    // 枚举状态
    for (int state = 1; state < (1 << n); state++) {
        // 枚举当前城市
        for (int i = 0; i < n; i++) {
            if ((state & (1 << i)) == 0) continue;
            // 枚举下一个城市
            for (int j = 0; j < n; j++) {
                if ((state & (1 << j)) != 0) continue;
                int nextState = state | (1 << j);
                if (dp[state][i] != Integer.MAX_VALUE) {
                    dp[nextState][j] = Math.min(dp[nextState][j], 
                                              dp[state][i] + cost[i][j]);
                }
            }
        }
    }
    
    return dp[(1 << n) - 1][n-1];
}
def tsp(cost: List[List[int]]) -> int:
    n = len(cost)
    dp = [[float('inf')] * n for _ in range(1 << n)]
    dp[1][0] = 0  # 初始在城市0
    
    # 枚举状态
    for state in range(1, 1 << n):
        # 枚举当前城市
        for i in range(n):
            if not (state & (1 << i)): continue
            # 枚举下一个城市
            for j in range(n):
                if state & (1 << j): continue
                next_state = state | (1 << j)
                dp[next_state][j] = min(dp[next_state][j], 
                                      dp[state][i] + cost[i][j])
    
    return dp[(1 << n) - 1][n-1]

集合划分

算法思想

  1. 定义dp[state]表示状态为state的最优解
  2. 枚举state的子集sub
  3. 状态转移方程:dp[state] = min(dp[state], dp[state^sub] + cost[sub])
  4. 最终答案为dp[(1<<n)-1]

代码实现

int partition(vector<int>& nums) {
    int n = nums.size();
    vector<int> dp(1 << n, INT_MAX);
    dp[0] = 0;
    
    // 预处理每个子集的代价
    vector<int> cost(1 << n);
    for (int state = 0; state < (1 << n); state++) {
        for (int i = 0; i < n; i++) {
            if (state & (1 << i)) {
                cost[state] += nums[i];
            }
        }
    }
    
    // 枚举状态
    for (int state = 0; state < (1 << n); state++) {
        // 枚举子集
        for (int sub = state; sub; sub = (sub - 1) & state) {
            if (dp[state^sub] != INT_MAX) {
                dp[state] = min(dp[state], dp[state^sub] + cost[sub]);
            }
        }
    }
    
    return dp[(1 << n) - 1];
}
public int partition(int[] nums) {
    int n = nums.length;
    int[] dp = new int[1 << n];
    Arrays.fill(dp, Integer.MAX_VALUE);
    dp[0] = 0;
    
    // 预处理每个子集的代价
    int[] cost = new int[1 << n];
    for (int state = 0; state < (1 << n); state++) {
        for (int i = 0; i < n; i++) {
            if ((state & (1 << i)) != 0) {
                cost[state] += nums[i];
            }
        }
    }
    
    // 枚举状态
    for (int state = 0; state < (1 << n); state++) {
        // 枚举子集
        for (int sub = state; sub > 0; sub = (sub - 1) & state) {
            if (dp[state^sub] != Integer.MAX_VALUE) {
                dp[state] = Math.min(dp[state], dp[state^sub] + cost[sub]);
            }
        }
    }
    
    return dp[(1 << n) - 1];
}
def partition(nums: List[int]) -> int:
    n = len(nums)
    dp = [float('inf')] * (1 << n)
    dp[0] = 0
    
    # 预处理每个子集的代价
    cost = [0] * (1 << n)
    for state in range(1 << n):
        for i in range(n):
            if state & (1 << i):
                cost[state] += nums[i]
    
    # 枚举状态
    for state in range(1 << n):
        # 枚举子集
        sub = state
        while sub:
            dp[state] = min(dp[state], dp[state^sub] + cost[sub])
            sub = (sub - 1) & state
    
    return dp[(1 << n) - 1]

时间复杂度分析

算法时间复杂度空间复杂度
旅行商问题 O ( n 2 ⋅ 2 n ) \mathcal{O}(n^2 \cdot 2^n) O(n22n) O ( n ⋅ 2 n ) \mathcal{O}(n \cdot 2^n) O(n2n)
集合划分 O ( 3 n ) \mathcal{O}(3^n) O(3n) O ( 2 n ) \mathcal{O}(2^n) O(2n)

应用场景

  1. 旅行商问题
  2. 集合划分问题
  3. 状态压缩搜索
  4. 子集枚举
  5. 图的最小支配集

注意事项

  1. 状态表示方法
  2. 位运算技巧
  3. 状态转移正确性
  4. 内存限制
  5. 计算复杂度

经典例题

  1. 郊区春游
  2. 数位染色
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值