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

动态规划算法:状态压缩

状态压缩动态规划算法

状态压缩动态规划是动态规划的一种,它通过使用位运算的方式压缩程序占用的空间,对于可以用来解决一些只有两个状态(是与否)的问题。
多少无益,我们通过下面的一道编程题目来学习这种算法。

题目描述:
小明使用霰弹枪打固定靶,假设有M个目标,小明开了N枪。每一枪都有若干目标被命中。现在小明想知道,自己刚刚理论上最少只需要几枪就能打中全部的目标。

假设有3个目标:[0, 0, 0]
小明开了3枪:[1, 0, 0],[0, 1, 0],[0, 1, 1],其中为1表示命中了对应的目标。
结果输出:小明第一枪加第三枪命中了所有的目标,理论上只要两枪就可以全部命中。

假设有3个目标:[0, 0, 0]
小明开了3枪:[1, 0, 0],[0, 1, 0],[1, 1, 0],其中为1表示命中了对应的目标。
结果输出:小明无法全部命中目标。

分析:
这道题有很多不同解法,可以使用贪心算法,但是为了讲解状压动规,这里不使用贪心算法。
使用动态规划解决问题的第一步就是要确定使用动态规划能否解决问题。题干中有一个关键词,最少几枪。更关键的一点是它没有要求我输出到底是哪几枪(如果要求输出哪几枪可能就要用回溯子集了)。因此大家应该条件反射的尝试使用动态规划。
动态规划最重要的一个步骤就是确定动态数组dp与状态转移矩阵。

确定dp数组的含义
首先确认dp的大小,有的同学可能条件反射的认为dp的大小应该是M,对应M个目标。dp[i]表示命中i个目标需要的最小枪数。但是接下来会发现举步维艰,因为都是命中i个目标,如果打中不一样的靶子,反应的是不同的状态。实际上这道题的dp矩阵的索引的含义并不是直接了当的呈现在我们的面前。
提问:当我有M个目标时,我有多少种命中状态?
答:每个目标都有命中与未命中两种状态,M个目标组合起来应该是2的M次方。
在这里插入图片描述
聪明的读者到这里应该恍然大悟一下:原来dp要有2^M个元素,索引反应了目标的命中情况。假设dp[3] = 2,indx = 3的二进制是011b,这里表示命中前两个目标最少需要2枪。

dp数组初始化
明确了dp数组的含义,就需要对dp数组进行初始化了,结合dp数组的含义,我们应该首先明确:
dp[0] = 0:命中0个目标需要开0枪。
假设小明第2枪命中情况为:[1, 0, 1],那么:
dp[101b] = dp[5] = 1:命中第一个与第三个目标只需要一枪。
我们将命中目标用二进制掩码表示,将所有子弹命中的情况对应的dp[indx]置为1即可完成初始化。
这就是这个问题的解决思路,现在我们直接上代码,一些细节用代码来讲述。

完整代码

#include <iostream>
#include <vector>
#include <string>
#include <climits>
using namespace std;int main() {int M, N;		// M个目标 开N枪cin >> M >> N;	// 输入 M 与 Nvector<int> state;	// 装填掩码for (int i = 0; i < N; i++) {int s = 0;for (int j = 0; j < M; j++) {int tmp = 0;cin >> tmp;		//  输入第i枪命中情况,1:命中,0:未命中s = (s << 1) + tmp;}state.push_back(s);}// 对state内的所有数据按位取与,如果tmp = 2^M -1,证明N枪后所有目标都命中int tmp = 0;for (int i : state) {tmp |= i;}if (tmp != (1 << M) - 1) {	cout << "未命中所有目标" << endl;return 0;}// 初始化dp数组int num = (1 << M) - 1;vector<int> dp(num + 1, INT_MAX);dp[0] = 0;for (int i : state) {dp[i] = 1;}// 状态转移,讲解见后文for (int mask : state) {for (int indx = num; indx >= 0; indx--) {if (dp[indx] != INT_MAX) {int new_indx = indx | mask;if (dp[indx] + 1 < dp[new_indx]) {dp[new_indx] = dp[indx] + 1;}}}}cout << "最少需要" << dp[num] << "枪" << endl;return 0;
}

状态转移分析

假设当前命中状态为x0,在第k发子弹的射击下,命中状态x1变为x1 = x0 | state[k];
从状态x0到状态x1,射击次数变为:tmp_num = dp[x0] + 1
结合dp[x1]的定义:命中状态为x1时,需要最少的的射击次数。因此我们需要比较通过x0转移到x1的射击次数tmp_numdp[x1]目前的值:
伪代码:

int x1 = x0 | state[k];	// 这里表示一次射击动作
int tmp_num = dp[x0] + 1;
if(tmp_num < dp[x1]) { dp[x1] = tmp_num;	// 证明由x0到达x1比原本的方案更快 }
else { ;	// 空操作,证明当前到达x1有更快的射击方案 }

小结

上面的讲解就是状态压缩动态规划的一个使用案例与核心逻辑。
之所以选择这种方法有三个关键要素:
1、求最优方案。
2、不要求输出最优方案的具体路径信息。
3、对于最小元素(一个目标),只有是和否(命中/未命中)两种区别。

相关文章:

  • 【python编程从入门到到实践】第二章 变量和简单的数据类型
  • Nginx 文件上传大小限制及 `client_max_body_size` 最大值详解
  • Linux 系统盘制作 | 引导加载器(GRUB 为例)| mount
  • 二叉树进阶 - 二叉搜索树
  • PDF转excel+json ,vue3+SpringBoot在线演示+附带源码
  • 宇树机器狗go2—slam建图(1)点云格式
  • MLLMs for TSAD ?
  • 单例模式:懒汉式的两种优化写法
  • 编译报错 宏 _IOC_SIZEBITS,而这个宏在编译时未定义
  • Bash 中的数学运算详解
  • 【每天一个知识点】模式识别
  • 自动驾驶---决策规划之导航增强端到端
  • Jinja2模板引擎SSTI漏洞
  • 加密壳(二)将shellcode写入PE
  • STL——红黑树的封装及map/set的模拟实现
  • 数字孪生火星探测车,星际探索可视化
  • 泛目录二级目录【实用指南】,无极站群系统2025升级版
  • leetcode125.验证回文串
  • java蓝桥杯b组
  • 20-算法打卡-哈希表-赎金信-leetcode(383)-第二十天
  • 建投读书会·东西汇流|西风东渐中的上海营造
  • 亲诚惠容行大道,命运与共开新篇——中共中央政治局委员、外交部长王毅谈习近平主席对越南、马来西亚、柬埔寨进行国事访问
  • 关注“老旧小区加装电梯”等安全隐患,最高检发布相关典型案例
  • 上海浦东打造全新开放平台,年内实现基本功能落地运行
  • 习近平抵达金边对柬埔寨进行国事访问
  • 河北钢企都拯救不了!英国高炉要灭?或成G7中唯一无法产原生钢国家