当前位置: 首页 > news >正文

状压 dp --- 数据范围小

hello,大家好,我们又见面了!今天是2025年9月6日,我们继续开启 状压 dp 的学习。

一:简介

状压 dp 常用于解决的第三种问题就是当数据范围很小但是暴搜的话会超时。

这类问题相对于前两类问题比较灵活,但是也是有明显的特征:属于 n 很小,但是暴搜的话即使加上优化和剪枝也会超时,因此,我们可以往状压 dp 的方向去思考。

接下来的两大题目都会讲到之前没有了解过的新东西,既然大家点进来了,就不要跳过了,我相信大家看完后一定会有所收获。

二:经典题目

题目一:糖果

题目链接:糖果

在这道题目中,会讲述一种之前我们很少用到的推导状态转移方程的方式,希望大家一定不要跳过

【题目描述】

【算法原理】

解法一:状态压缩 + 最短路 + BFS(最优解)(这个解法前面已经讲过了,就不再赘述了)

解法二:状压 dp

对于这类数据范围很小的问题,我们首先应该想到的解法就是暴力搜索。但是这道题目暴搜策略不好想而且时间复杂度会非常高,因此我们考虑使用状压 dp 来解决这道问题。

1.状态表示:(经验 + 题目要求)

f[x]表示:凑成糖果的口味状态为 x 时,最少需要多少包糖果。

2.最终结果:

如果这样定义状态表示的话,根据题目要求,最终结果显然是 f[2 ^ m - 1];

3.状态转移方程:(考虑最近的一步来分析问题)

我们考虑最近的一步,看一看有哪些状态可以通过再购买一包糖果后更新到 x 上。

这样更新状态的话要保证 x 之前的状态全部已经计算出来了。但是我们发现,前面的状态并不好确定。对于一包糖果而言,可以找到很多前驱状态。

因此,我们换一种思维方式,使用 f[x] 去更新后继节点(类似于拓扑排序),这样的话,状态转移方程就很好推导了,直接将某一包的糖果状态按位或进去就行了。

f[x | a[i]] = min(f[x | a[i]],f[x] + 1); 

4.初始化:

所有格子初始化为正无穷,f[0] = 0 即可。(最初的状态 0 去更新后面的状态)

5.填表顺序:

保证 f[x] 更新完毕之后然后才能去更新后继结点,因此我们只需保证从大到小枚举状态即可。

最后解释一个问题:为什么这道题可以使用状压 dp 而《关灯问题II》这道题目却不能使用呢?

因为本题状态是不会回退的,《关灯问题II》这道题目状态有可能回退,图中会出现环路,存在环路时不能使用动态规划的!!!

【代码实现】

#include <iostream>
#include <cstring>using namespace std;const int N = 110, M = 21;int n, m, k;
int a[N];int f[1 << M];int main()
{cin >> n >> m >> k;for(int i = 0; i < n; i++)for(int j = 0; j < k; j++){int x; cin >> x; x--;a[i] |= (1 << x);}memset(f, 0x3f, sizeof f);f[0] = 0;// s 一定要从 0 开始,因为我们要用 0 去更新后面的值 for(int s = 0; s < (1 << m); s++)for(int i = 0; i < n; i++)f[s | a[i]] = min(f[s | a[i]], f[s] + 1);if(f[(1 << m) - 1] == 0x3f3f3f3f) cout << -1 << endl;else cout << f[(1 << m) - 1] << endl; return 0;
}

题目二:PRZ

题目链接:PRZ

【题目描述】

解释:一号和二号为一组,过桥时间为 24,三号自己一组,过桥时间为 18,所有人过桥的总时间最优为 24 + 18 = 42。

【前置知识】(很重要)

如何枚举一个状态的子状态:

类比我们之前学过的枚举子集:

枚举一个状态的子状态:

枚举方式:

for(int x = 0; x < (1 << n); x++)for(int y = x; ; y = x & (y - 1)){// y 就是 x 这个状态的一个子状态if(!y) break;}

解释:

【算法原理】

本题数据范围很小,但是暴搜的话很难找到一种剪枝策略,因为这道题要考虑的因素比较多。

我们尝试使用 状压 dp 的方式来解决一下这道问题:

1.状态表示(经验 + 题目要求):

f[x] 表示:已经过桥人的状态为 x 时,所花费的最少时间。

2.最终结果:

根据状态表示以及题目要求,显然最终结果为 f[(1 << n) - 1]。

3.状态转移方程(根据最近的一步分情况讨论):

假设当前过桥状态 x 为 1,2,4,6,7 已过桥:

根据最后一组过桥的人分情况讨论:

我们枚举他们这些人中所有的子集 y:

当W[x ^ y] <= w 时(保证剩下的那些人能一次过桥),f[x] = min(f[x],f[y] + T[x ^ y]);

4.初始化:

因为我们要求的是最小值,因此所有格子的值先初始化为正无穷,f[0] = 0;

5.填表顺序:

枚举子集就可以了~~~。

【代码实现】

#include <iostream>
#include <cstring>using namespace std;const int N = 18;int m, n;
int t[N], w[N];int T[1 << N], W[1 << N], f[1 << N];int main()
{cin >> m >> n;for(int i = 0; i < n; i++) cin >> t[i] >> w[i];// 预处理出每一个状态的最大时间和总重for(int s = 0; s < (1 << n); s++)for(int i = 0; i < n; i++){if((s >> i) & 1){T[s] = max(T[s], t[i]);W[s] += w[i];}} memset(f, 0x3f, sizeof f);f[0] = 0;for(int x = 0; x < (1 << n); x++)for(int y = x; ; y = x & (y - 1)){if(W[x ^ y] <= m) f[x] = min(f[x], f[y] + T[x ^ y]);if(!y) break;}cout << f[(1 << n) - 1] << endl;return 0;
}

好的,今天的分享就先到这里了,大家再见。


文章转载自:

http://NDfl5Svz.jcxgr.cn
http://uAfWKulC.jcxgr.cn
http://Ws9Tm2fV.jcxgr.cn
http://H4OZK5dd.jcxgr.cn
http://hyUhHNu2.jcxgr.cn
http://9I7e7Uvb.jcxgr.cn
http://79ik2fL3.jcxgr.cn
http://za3st5YN.jcxgr.cn
http://p6zY5Dkd.jcxgr.cn
http://b3VhswnB.jcxgr.cn
http://bAUp0qCv.jcxgr.cn
http://EtJcOXK3.jcxgr.cn
http://guJabJsv.jcxgr.cn
http://jQ4ebFVE.jcxgr.cn
http://JXI0nV04.jcxgr.cn
http://Y8APrw5b.jcxgr.cn
http://TdKo0jLO.jcxgr.cn
http://jBn4cAgW.jcxgr.cn
http://fLVNrMey.jcxgr.cn
http://qmavC86T.jcxgr.cn
http://2HiSVLQW.jcxgr.cn
http://tt1HZVTh.jcxgr.cn
http://ROzMSoXf.jcxgr.cn
http://ulZXwWKh.jcxgr.cn
http://H7Z680SF.jcxgr.cn
http://4IJZ5TX5.jcxgr.cn
http://rCF30LK3.jcxgr.cn
http://VEYwN6Qx.jcxgr.cn
http://uwnEAwIs.jcxgr.cn
http://2q6fpyvv.jcxgr.cn
http://www.dtcms.com/a/371147.html

相关文章:

  • 赋值与深浅拷贝
  • Dart核心语言基础 集合 Map使用指南
  • 下载数据集用于图像分类并自动分为训练集和测试集方法
  • LLM 长上下文 RAG
  • 深入剖析Spring Boot启动流程
  • 郭平《常变与长青》读书笔记(第二章)
  • 郭平《常变与长青》读书笔记(第四章)
  • 中断和异常
  • 压缩空气储能电站可视化管理
  • 第08章 聚合函数
  • 20250906-01:开始创建LangChain的第一个项目
  • MySQL数据库安全:权限管理与防SQL注入完全指南
  • 吴恩达机器学习(八)
  • 50系显卡训练深度学习YOLO等算法报错的解决方法
  • 【golang长途旅行第38站】工厂模式
  • 分享vscode翻译插件
  • Phthon3 学习记录-0707
  • 嵌入式学习笔记--Linux系统编程阶段--DAY07进程间通信--存储映射和共享内存
  • DMA寄存器学习
  • 对于单链表相关经典算法题:206. 反转链表及876. 链表的中间结点的解析
  • 云原生部署_k8s入门
  • 分布式数据库的历史演变与核心原理
  • 线代:排列与逆序
  • GPIO的配置中开漏输出与推挽输出的差别
  • 有有有深度学习
  • 车载通信架构 --- DoIP企业规范中细节有哪些?
  • 【Linux基础】Linux系统管理:GPT分区实践详细操作指南
  • 6-2-4 解决第一次发送失败
  • 跨域彻底讲透
  • c++之基础B(x转10进制,含十六进制)(第四课)