7.4状压DP

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

一、状压DP的核心思想

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

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

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

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

二、状压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,表示只访问了城市0

    for (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; // 取最低位的1
            available ^= 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;
                }
            }
        }
    }
    
    // 状压DP
    vector<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的优化技巧

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/965362.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vs code 使用教程

一、定义 多行注释vs 找不到上层文件路径选择 或 创建python 虚拟环境git 远程克隆及推送vs code 文件路径vs 使用tensorboard 二、使用 学习网站&#xff1a;https://learn.microsoft.com/zh-cn/visualstudio/python/?viewvs-2022性能分析&#xff1a;https://learn.micros…

【Elasticsearch】terms聚合误差问题

Elasticsearch中的聚合查询在某些情况下确实可能存在误差&#xff0c;尤其是在处理分布式数据和大量唯一值时。这种误差主要来源于以下几个方面&#xff1a; 1.分片数据的局部性 Elasticsearch的索引通常被分成多个分片&#xff0c;每个分片独立地计算聚合结果。由于数据在分…

BUU22 [护网杯 2018]easy_tornado 1

打开题目以后出现三个文件&#xff0c;查看源代码&#xff0c;突破口在于这三个文件都有特殊的格式 python的tornado漏洞 Tornado 是一个用 Python 编写的 Web 框架&#xff08;和flask一样&#xff0c;只不过flask是轻量级的&#xff0c;而tornado可以处理高流量&#xff09…

QT修仙之路1-1--遇见QT

文章目录 遇见QT二、QT概述2.1 定义与功能2.2 跨平台特性2.3 优点汇总 三、软件安装四、QT工具介绍(重要)4.1 Assistant4.2 Designer4.3 uic.exe4.4 moc.exe4.5 rcc.exe4.6 qmake4.7 QTcreater 五、QT工程项目解析(作业)5.1 配置文件&#xff08;.pro&#xff09;5.2 头文件&am…

寒假2.5

题解 web:[网鼎杯 2020 朱雀组]phpweb 打开网址&#xff0c;一直在刷新&#xff0c;并有一段警告 翻译一下 查看源码 每隔五秒钟将会提交一次form1&#xff0c;index.php用post方式提交了两个参数func和p&#xff0c;func的值为date&#xff0c;p的值为Y-m-d h:i:s a 执行fu…

计算机中数值表示:原码、反码、补码与移码

1 前言 计算机科学中&#xff0c;数字的表示方式至关重要&#xff0c;因为计算机内部只能识别处理二进制数据。为了在计算机中实现对整数的表示&#xff0c;提出了多种数值编码方式&#xff0c;其中最常用的是原码、反码、补码和移码。 2 原码 2.1 原码的定义 原码(Signed …

硬件实现I2C常用寄存器简单介绍

引言 在深入探讨I2C外设的具体案例之前&#xff0c;理解其核心寄存器的配置至关重要。这些寄存器不仅控制着I2C模块的基本操作模式&#xff0c;如数据传输速率和地址识别&#xff0c;还负责管理更复杂的通信需求&#xff0c;例如中断处理、DMA交互及错误检测与恢复。接下来的内…

分析用户请求K8S里ingress-nginx提供的ingress流量路径

前言 本文是个人的小小见解&#xff0c;欢迎大佬指出我文章的问题&#xff0c;一起讨论进步~ 我个人的疑问点 进入的流量是如何自动判断进入iptables的四表&#xff1f;k8s nodeport模式的原理&#xff1f; 一 本机环境介绍 节点名节点IPK8S版本CNI插件Master192.168.44.1…

linux中,软硬链接的作用和使用

一、软硬链接的作用 软硬链接&#xff0c;是大家所熟系的内容了。链接就是方便人使用电脑上访问文件、方便进程访问文件的工具。比如软连接大家都有见过&#xff0c;在安装某款软件的时候要不要添加快捷方式。在windows系统上&#xff0c;我们右键点击文件的时候按‘s’就能创建…

kalman滤波器C++设计仿真实例第三篇

1. 仿真场景 水面上有条船在做匀速直线航行&#xff0c;航行过程中由于风和浪的影响&#xff0c;会有些随机的干扰&#xff0c;也就是会有些随机的加速度作用在船身上&#xff0c;这个随机加速度的均方差大约是0.1&#xff0c;也就是说方差是0.01。船上搭载GPS设备&#xff0c;…

ubuntu20.04+RTX4060Ti大模型环境安装

装显卡驱动 这里是重点&#xff0c;因为我是跑深度学习的&#xff0c;要用CUDA&#xff0c;所以必须得装官方的驱动&#xff0c;Ubuntu的附件驱动可能不太行. 进入官网https://www.nvidia.cn/geforce/drivers/&#xff0c;选择类型&#xff0c;最新版本下载。 挨个运行&#…

[c语言日寄]浮点数在内存中的储存

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

Yageo国巨的RC系列0402封装1%电阻库来了

工作使用Cadence多年&#xff0c;很多时候麻烦的就是整理BOM&#xff0c;因为设计原理图的时候图省事&#xff0c;可能只修改value值和封装。 但是厂家&#xff0c;规格型号&#xff0c;物料描述等属性需要在最后的时候一行一行的修改&#xff0c;繁琐又容易出错&#xff0c;过…

【文档智能】Qwen2.5-VL在版式分析和表格识别上的实际评测效果

qwen开年开源了Qwen2.5-VL系列权重模型&#xff0c;笔者观察到相较于传统的多模态系列&#xff0c;增加了文档理解功能。笔者以文档智能中两个比较重要的任务版式分析和表格识别&#xff0c;笔者直接测试下Qwen2.5-VL-72B的效果。 版式分析 case1 case2 这个case没有输出bbox…

【计算机组成原理】1_绪论

chap1 绪论 1. 国产芯片现状 MIPS阵营&#xff1a;龙芯X86阵营&#xff08;常见于桌面和服务器&#xff09;&#xff1a;兆芯&#xff08;VIA&#xff09;&#xff0c;海光&#xff08;AMD&#xff09;ARM阵营&#xff08;常见于移动嵌入式、手机平板等&#xff09;&#xff…

解锁反序列化漏洞:从原理到防护的安全指南

目录 前言 一、什么是反序列化 二、反序列化漏洞原理 三、反序列化漏洞的危害 &#xff08;一&#xff09;任意代码执行 &#xff08;二&#xff09;权限提升 &#xff08;三&#xff09;数据泄露与篡改 四、常见的反序列化漏洞场景 &#xff08;一&#xff09;PHP 反…

openGauss 3.0 数据库在线实训课程1:学习数据库状态查看

openGauss数据库状态查看 前提 我正在参加21天养成好习惯| 第二届openGauss每日一练活动 课程详见&#xff1a;openGauss 3.0.0数据库在线实训课程 学习目标 学习从操作系统层面和使用openGauss工具查看数据库的状态、版本和数据文件目录。 课程作业 gs_ctl是openGauss提…

[含文档+PPT+源码等]精品基于Python实现的django个性化健康餐计划订制系统

软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;python 使用框架&#xff1a;Django 前端技术&#xff1a;JavaScript、VUE.js&#xff08;2.X&#xff09;、css3 开发工具&#xff1a;pycharm、Visual Studio Code、HbuildX 数据库&#xff1a;MySQL 5.7.26&am…

单机伪分布Hadoop详细配置

目录 1. 引言2. 配置单机Hadoop2.1 下载并解压JDK1.8、Hadoop3.3.62.2 配置环境变量2.3 验证JDK、Hadoop配置 3. 伪分布Hadoop3.1 配置ssh免密码登录3.2 配置伪分布Hadoop3.2.1 修改hadoop-env.sh3.2.2 修改core-site.xml3.2.3 修改hdfs-site.xml3.2.4 修改yarn-site.xml3.2.5 …

ZooKeeper单节点详细部署流程

ZooKeeper单节点详细部署流程 文章目录 ZooKeeper单节点详细部署流程 一.下载稳定版本**ZooKeeper**二进制安装包二.安装并启动**ZooKeeper**1.安装**ZooKeeper**2.配置并启动**ZooKeeper** ZooKeeper 版本与 JDK 兼容性3.检查启动状态4.配置环境变量 三.可视化工具管理**Zooke…