7.4状压DP

ops/2025/2/7 13:35:15/

在C++中,状态压缩动态规划(State Compression DP,简称状压DP 是一种通过 二进制位运算 高效表示离散状态集合的动态规划方法,特别适用于解决 组合优化排列选择 类问题。其核心思想是将多维状态压缩为整数,利用位操作快速进行状态转移。以下是状压DP的详细解析与实战指南:

DP_2">一、状压DP的核心思想

  1. 状态表示
    用二进制数的每一位(bit)表示某个元素的 存在性状态。例如:

    • 00101 表示第0位和第2位被选中(从右向左数)
    • mask = 1 << k 表示第k位被激活
  2. 适用场景

    • 元素数量较少(通常 n ≤ 20,因为 2^20 ≈ 1e6 在内存可接受范围内)
    • 需要快速判断元素之间的组合关系(如覆盖、冲突、依赖)
  3. 优势

    • 将多维状态压缩为一维整数,简化状态管理
    • 利用位运算高效处理状态转移(与、或、异或、移位)

DP_19">二、状压DP的解题步骤

  1. 定义状态
    确定状态变量 dp[mask],其中 mask 是一个二进制整数,表示当前已选/已访问的元素组合。

  2. 初始化状态
    通常 dp[0] = 0 或根据问题需求设置初始值。

  3. 状态转移方程
    遍历所有可能的状态转移路径,更新 dp[mask]。例如:

    for (int mask = 0; mask < (1 << n); ++mask) {for (int i = 0; i < n; ++i) {if (!(mask & (1 << i))) {  // 第i位未被选中int new_mask = mask | (1 << i);dp[new_mask] = min(dp[new_mask], dp[mask] + cost[i]);}}
    }
    
  4. 提取结果
    最终答案通常位于 dp[(1 << n) - 1](全选状态)或特定状态。


三、位运算技巧

1. 基本操作
操作代码说明
检查第k位是否为1mask & (1 << k)结果为非0表示存在
设置第k位为1`mask(1 << k)`
设置第k位为0mask & ~(1 << k)取消选中第k个元素
切换第k位状态mask ^ (1 << k)取反第k
统计1的个数__builtin_popcount(mask)GCC内置函数
2. 高级操作
  • 枚举子集:遍历所有子集(用于集合覆盖类问题)

    for (int subset = mask; subset; subset = (subset - 1) & mask) {// 处理子集subset
    }
    
  • 快速判断状态转移可行性:检查是否满足约束条件(如无冲突)


四、经典问题与代码实现

1. 旅行商问题(TSP)

问题:访问所有城市恰好一次,求最短路径。
状态定义dp[mask][u] 表示已访问城市集合为mask,当前位于城市u的最短路径。
状态转移dp[mask | (1 << v)][v] = min(dp[mask][u] + dist[u][v])

int tsp(vector<vector<int>>& dist) {int n = dist.size();vector<vector<int>> dp(1 << n, vector<int>(n, INT_MAX));// 初始化:从起点0出发dp[1][0] = 0;  // mask=000...001,表示只访问了城市0for (int mask = 1; mask < (1 << n); ++mask) {for (int u = 0; u < n; ++u) {if (!(mask & (1 << u))) continue;  // u不在当前路径中for (int v = 0; v < n; ++v) {if (mask & (1 << v)) continue; // v已被访问过int new_mask = mask | (1 << v);dp[new_mask][v] = min(dp[new_mask][v], dp[mask][u] + dist[u][v]);}}}// 返回从终点回到起点的最短路径int final_mask = (1 << n) - 1;int res = INT_MAX;for (int u = 0; u < n; ++u) {if (dp[final_mask][u] != INT_MAX && dist[u][0] != INT_MAX) {res = min(res, dp[final_mask][u] + dist[u][0]);}}return res;
}

2. 棋盘覆盖问题(LeetCode 52. N皇后 II)

问题:在n x n棋盘放置皇后,使得彼此不能互相攻击,求方案数。
状态定义:用位掩码表示列、主对角线、副对角线的占用情况。

int totalNQueens(int n) {int count = 0;function<void(int, int, int, int)> dfs = [&](int row, int cols, int diag1, int diag2) {if (row == n) {count++;return;}// 计算当前行可放置的位置int available = ((1 << n) - 1) & ~(cols | diag1 | diag2);while (available) {int pos = available & -available; // 取最低位的1available ^= pos; // 移除该位置dfs(row + 1, cols | pos, (diag1 | pos) << 1, (diag2 | pos) >> 1);}};dfs(0, 0, 0, 0);return count;
}

3. 最小顶点覆盖(LeetCode 943. 最短超级串)

问题:合并多个字符串为一个最短超级串,要求所有字符串都是其子串。
状态定义dp[mask][i] 表示已选字符串集合为mask,最后一个字符串为i时的最小长度。

string shortestSuperstring(vector<string>& words) {int n = words.size();vector<vector<int>> overlap(n, vector<int>(n, 0));// 预处理计算两两字符串的重叠长度for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {if (i == j) continue;int len = min(words[i].size(), words[j].size());for (int k = len; k >= 0; --k) {if (words[i].substr(words[i].size() - k) == words[j].substr(0, k)) {overlap[i][j] = k;break;}}}}// 状压DPvector<vector<int>> dp(1 << n, vector<int>(n, INT_MAX));vector<vector<int>> parent(1 << n, vector<int>(n, -1));// 初始化:只选一个字符串for (int i = 0; i < n; ++i) {dp[1 << i][i] = words[i].size();}// 状态转移for (int mask = 1; mask < (1 << n); ++mask) {for (int last = 0; last < n; ++last) {if (!(mask & (1 << last))) continue;for (int next = 0; next < n; ++next) {if (mask & (1 << next)) continue;int new_mask = mask | (1 << next);int new_len = dp[mask][last] + words[next].size() - overlap[last][next];if (new_len < dp[new_mask][next]) {dp[new_mask][next] = new_len;parent[new_mask][next] = last;}}}}// 回溯构造结果int min_len = INT_MAX, last = -1;int full_mask = (1 << n) - 1;for (int i = 0; i < n; ++i) {if (dp[full_mask][i] < min_len) {min_len = dp[full_mask][i];last = i;}}// 根据parent数组重建路径vector<int> path;int mask = full_mask;while (last != -1) {path.push_back(last);int prev = parent[mask][last];mask ^= (1 << last);last = prev;}reverse(path.begin(), path.end());// 拼接字符串string res = words[path[0]];for (int i = 1; i < path.size(); ++i) {int overlap_len = overlap[path[i-1]][path[i]];res += words[path[i]].substr(overlap_len);}return res;
}

DP_222">五、状压DP的优化技巧

  1. 预处理减少计算
    提前计算必要信息(如字符串重叠长度、冲突关系),避免在DP循环中重复计算。

  2. 滚动数组优化空间
    若状态转移仅依赖前一层的状态,可用两个数组交替使用减少内存占用:

    vector<int> prev(1 << n), curr(1 << n);
    
  3. 剪枝策略
    跳过不可能达到更优解的状态(如当前路径已超过已知最短长度)。

  4. 对称性优化
    利用问题对称性减少状态数量(如旋转、镜像对称的棋盘状态)。


六、常见错误与调试技巧

  1. 位运算优先级错误
    使用括号明确运算顺序,例如 mask & (1 << k) == 0 应写为 (mask & (1 << k)) == 0

  2. 状态空间过大
    n > 20 时,考虑其他算法(如启发式搜索)或问题特定优化。

  3. 初始化遗漏
    确保所有可能的初始状态正确设置(如 dp[1<<i][i] = base_value)。

  4. 调试方法

    • 打印关键状态的值和转移路径
    • 对小规模输入(如 n=3)手动验证
    • 使用断言检查中间结果合法性

七、典型应用场景

问题类型特点示例问题
排列组合优化选择元素的最优排列方式TSP、任务调度
棋盘覆盖处理行列、对角线的冲突N皇后、数独
集合覆盖选择子集满足覆盖条件最小顶点覆盖、集合覆盖
位操作依赖状态转移依赖位运算结果子集生成、位掩码权限控制

八、实战训练建议

  1. 从简单问题入手
    先掌握基础状态表示(如子集选择),再挑战复杂问题(如TSP)。

  2. 模板化代码结构
    将状态循环、位操作封装为可复用的代码块。

  3. 理解问题本质
    分析状态转移的物理意义,避免盲目套用模板。

  4. 练习经典题目

    • LeetCode 464. 我能赢吗
    • LeetCode 691. 贴纸拼词
    • LeetCode 1434. 每个人戴不同帽子的方案数

状压DP的难点在于 将问题抽象为位掩码状态设计高效的状态转移方程。通过大量练习和总结,能够逐步掌握这一高效算法的精髓。


http://www.ppmy.cn/ops/156443.html

相关文章

项目练习:SpringSecurity+OAuth2接入gitee的第三方登陆(授权码模式)

文章目录 一、知识准备1、OAuth2的角色2、使用场景3、四种授权模式 二、案例实现1、gitee上注册应用2、直接通过手动发送http请求方式3、项目代码方式4、测试方法 一、知识准备 1、OAuth2的角色 1、资源所有者(Resource 0wner):即用户&#xff0c;资源的拥有人&#xff0c;想要…

Spring Boot Actuator与JMX集成实战

在微服务架构中&#xff0c;监控和管理应用的运行状态是至关重要的。Spring Boot Actuator 提供了一种便捷的方式来监控和管理 Spring Boot 应用&#xff0c;而 JMX&#xff08;Java Management Extensions&#xff09;则是一种用于管理 Java 应用的标准技术。本文将通过一个实…

大语言模型极速部署:Ollama 、 One-API、OpenWebUi 完美搭建教程

大语言模型极速部署&#xff1a;Ollama 、 One-API、OpenWebUi 完美搭建教程 本文将介绍如何通过命令行工具部署 Ollama 和 One-API 以及 OpenWebUi&#xff0c;帮助你快速搭建私有化大模型。 Ollama 是一个容器化工具&#xff0c;简化了大语言模型的管理与运行&#xff0c;支持…

TongSearch3.0.4.0安装和使用指引(by lqw)

文章目录 安装准备手册说明支持的数据类型安装控制台安装单节点(如需集群请跳过这一节)解压和启动开启X-Pack Security和生成p12证书&#xff08;之后配置内置密码和ssl要用到&#xff09;配置内置用户密码配置ssl&#xff08;先配置内置用户密码再配ssl&#xff09;配置控制台…

linux环境自动化golang项目启动脚本解析

一.场景介绍 当在本地创建了golang项目,修改了代码功能,怎么在远程测试服务器上更新该功能呢,可以使用下面的步骤来解决该问题(这只是其中一种方法): (1).推送最新代码到远程仓库 (2).在测试服务器上创建该项目并拉取最新代码 (3).创建deploy.sh脚本 (4).运行deploy.sh脚本 二.…

deepseek接入pycharm 进行AI编程

要将DeepSeek接入PyCharm进行AI编程,可以按照以下步骤操作: ### 1. 获取DeepSeek API访问权限 DeepSeek通常以API的形式对外提供服务,你需要在其官方网站注册账号,申请API访问权限。在申请通过后,会获得API密钥(API Key),这是后续调用API的关键凭证。 ### 2. 安装必要…

游戏引擎 Unity - Unity 下载与安装

Unity Unity 首次发布于 2005 年&#xff0c;属于 Unity Technologies Unity 使用的开发技术有&#xff1a;C# Unity 的适用平台&#xff1a;PC、主机、移动设备、VR / AR、Web 等 Unity 的适用领域&#xff1a;开发中等画质中小型项目 Unity 适合初学者或需要快速上手的开…

visual studio安装

一、下载Visual Studio 访问Visual Studio官方网站。下载 Visual Studio Tools - 免费安装 Windows、Mac、Linux 在主页上找到并点击“下载 Visual Studio”按钮。 选择适合需求的版本&#xff0c;例如“Visual Studio Community”&#xff08;免费版本&#xff09;&#x…