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]

时间复杂度分析

算法 时间复杂度 空间复杂度
旅行商问题
集合划分

应用场景

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

注意事项

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

经典例题

  1. 郊区春游
  2. 数位染色
全部评论

相关推荐

非堵塞&nbsp;IO、事件循环(Event&nbsp;Loop)和事件队列是现代&nbsp;JavaScript&nbsp;和&nbsp;Node.js&nbsp;应用程序中用于处理异步操作的核心概念。它们共同工作,使得在单线程环境下能够高效地处理输入/输出操作。以下是这些概念的详细解释:https://www.nowcoder.com/issue/tutorial?zhuanlanId=j572L2&amp;amp;uuid=19017e996e2444a8b05bf61a3285892f1.&nbsp;非堵塞&nbsp;IO非堵塞&nbsp;IO(Non-blocking&nbsp;IO)是一种输入输出操作的方式,它不会阻塞程序的执行。传统的阻塞&nbsp;IO&nbsp;会使得程序在等待一个操作完成时暂停执行,这可能导致效率低下。非堵塞&nbsp;IO&nbsp;则允许程序继续执行其他任务,直到数据准备好或者操作完成。在&nbsp;Node.js&nbsp;中,很多&nbsp;IO&nbsp;操作(如文件读取、数据库查询和网络请求等)都是非堵塞的。这意味着,发起一个&nbsp;IO&nbsp;操作后,Node.js&nbsp;不会等到操作完成才继续执行后面的代码,而是立即返回,待操作完成时,通过回调函数、Promises&nbsp;或&nbsp;async/await&nbsp;来处理结果。2.&nbsp;事件循环(Event&nbsp;Loop)事件循环(Event&nbsp;Loop)是&nbsp;JavaScript&nbsp;的一种机制,负责管理异步操作的运行。由于&nbsp;JavaScript&nbsp;是单线程的,事件循环的主要目的是协调执行栈(call&nbsp;stack)和事件队列(event&nbsp;queue),处理异步操作。事件循环的工作流程如下:执行栈(Call&nbsp;Stack):所有的&nbsp;JavaScript&nbsp;代码都是在执行栈中执行的。当前执行的任务会被压入栈中,完成后从栈中弹出。事件队列(Event&nbsp;Queue):当异步操作完成(如网络请求、定时器等),相应的回调函数会被放入事件队列中,等待执行栈闲暇时进行处理。事件循环的运行:事件循环会不断检查执行栈是否为空。如果栈为空,它会从事件队列中取出第一个事件,并将其执行(即执行对应的回调函数)。如果执行栈不为空,它会继续执行栈中的任务,直到栈清空。这个机制保证了&nbsp;JavaScript&nbsp;在处理异步任务时的高效性,不会因为等待&nbsp;IO&nbsp;操作而阻塞整个程序的执行。
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务