动态规划——状态压缩

状态压缩简介

状态压缩指的是,通过一串0-1码保存一个集合的状态,把一个集合压缩成一个整数,所以称为状态压缩。
例如,有一行棋子,它们的排列分别是:黑 白 白 黑 黑 白 黑 白
这就可以用10011010 ( 2 ) _{(2)} (2)来表示这个状态。
一般的,我们把自然认知的第一位,如上述例子的“黑”认为是二进制里面的“1”的话,把它放在二进制权值最低的一位。第二位放在权值倒数第二低的位置,以此类推。
这么转换是为了方便在调用状态,使用(>>或<<)的位操作符时更方便。
仍以上述例子为例,按照这个规律转换的话,应该有
\qquad\qquad\qquad\qquad\qquad\quad 01011001 ( 2 ) _{(2)} (2)

接下来我们通过一些例子来熟悉和了解状态压缩在动态规划题目里面的使用。

互不侵犯

P1896互不侵犯

题目描述

N x N 的棋盘里面放 K 个国王,使他们互不攻击,共有多少种摆放方案。
国王能攻击到它上下左右以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

输入格式

只有一行,包含两个数 N,K

输出格式

所得的方案数

输入

3 2

输出

16

解题思路

观察可以得出,当前行的方法总数,必然是在当前行每一个合法的状态下,对上一行全部合法状态的方法数求和。
例如 第二行 101000 101000 101000的状态下,对第一行能和这个状态共存的所有状态进行枚举,把它们的方法数全部加到这个dp储存单位里面,就得到了这个状态下的最大方法数。
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示前i行里面放了k个国王,并且第i行状态是j的方法数,那么,状态转移方程就是
d p [ i ] [ j ] [ k ] = s u m ( d p [ i − 1 ] [ j 1 ] [ k − c o u n t ( j ) ] ) dp[i][j][k]=sum(dp[i-1][j1][k-count(j)]) dp[i][j][k]=sum(dp[i1][j1][kcount(j)])分别对国王数量和方法数求和
在具体代码实现过程中,把状态压缩成一个整数储存于下标,有效帮助我们快速判断两个状态之间是否合法,这就是状态压缩的意义。

示例代码

#include<stdio.h> 
int sta[600],staN;
int MAX;
long long dp[10][512][100];
//先初始化第1行,由于n小于等于9,因此状态值的最大值是九位二进制数,111111111,如果用十进制来储存就是512
int count(int k){int counting=0;while(k>0){if(k%2)counting++;k>>=1;}return counting;
}
int main(){int n,kk;scanf("%d%d",&n,&kk);MAX=(1<<n)-1;for(int i=0;i<=MAX;i++){if((!(i&(i<<1)))&&count(i)<=kk){sta[++staN]=i;//记录可行的状态//此处状态就是idp[1][i][count(i)]=1;}}for(int i=2;i<=n;i++){//行数for(int j1=1;j1<=staN;j1++){//此行合法状态int j=sta[j1];for(int upj1=1;upj1<=staN;upj1++){//上一行合法状态int upj=sta[upj1];if((j&upj)||(j&(upj<<1))||(j&(upj>>1)))continue;for(int upNumber=0;count(j)+upNumber<=kk;upNumber++){//此行加上面的王之后不会超出总数dp[i][j][count(j)+upNumber]+=dp[i-1][upj][upNumber];}}}}long long sum=0;for(int i=1;i<=staN;i++)sum+=dp[n][sta[i]][kk];printf("%lld\n",sum);return 0;
} 

另外,此代码还有两个优化的方向,一个是对于下标 j j j,可以以数组储存状态,再通过下标调用数组里面的状态,即把 j 1 , u p j 1 j1,upj1 j1,upj1作为数组下标。
其二是把 d p [ 10 ] [ 512 ] [ 100 ] dp[10][512][100] dp[10][512][100]这个数组的第一维略去,仅保留 d p [ 512 ] [ 100 ] dp[512][100] dp[512][100],并通过用temp数组复制的方法储存这一行与上一行的值,压缩空间复杂度。
这两种优化留待读者自行证明

关灯问题

题目描述

现有 n 盏灯,以及 m 个按钮。每个按钮可以同时控制这 n 盏灯——按下了第 i 个按钮,对于所有的灯都有一个效果。按下 i 按钮对于第 j 盏灯,是下面 3 种效果之一:如果 a [ i ] [ j ] a[i][j] a[i][j]为1,那么当这盏灯开了的时候,把它关上,否则不管;如果为−1 的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是 0,无论这灯是否开,都不管。
现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。

输入格式

前两行两个数,n, m。

输出格式

一个整数,表示最少按按钮次数。如果没有任何办法使其全部关闭,输出-1

输入

3
2
1 0 1
-1 1 0

输出

2

解题思路

依据状态压缩的思路,把每个灯泡的状态(开或关)用二进制表示, d p [ s t a t e ] dp[state] dp[state] 存储将当前状态 s t a t e state state 转变为目标状态(所有灯泡关掉)的最小操作次数。初始状态是所有灯泡开着,目标是将所有灯泡关掉。每个按钮会根据其影响(数组 a [ i ] [ j ] a[i][j] a[i][j] 中的 1 或 -1)翻转一些灯泡的状态,代码通过遍历所有状态,尝试按下每个按钮,计算新的状态并更新 dp 数组。最终,若 dp[0] 不为 INF,则输出最小操作次数,否则输出 -1,表示无法实现目标状态。

示例代码

#include <stdio.h>
#include <string.h>
#define MAXN 11
#define MAXM 101
#define INF 0x3f3f3f3f
int n, m;
int a[MAXM][MAXN];
int dp[1<<MAXN];  // 状态压缩dp,记录每个状态需要的最小操作次数
int min(int a, int b) {return a < b ? a : b;
}
int main() {scanf("%d%d", &n, &m);for(int i = 0; i < m; i++) {for(int j = 0; j < n; j++) {scanf("%d", &a[i][j]);}}// 初始化dp数组memset(dp, 0x3f, sizeof(dp));dp[(1<<n)-1] = 0;  // 初始状态所有灯都是开的// 枚举所有可能的状态for(int step = 0; step < (1<<n); step++) {  // 最多需要2^n次操作for(int state = 0; state < (1<<n); state++) {if(dp[state] == INF) continue;// 尝试按每个按钮for(int i = 0; i < m; i++) {int new_state = state;// 计算按下按钮i后的新状态for(int j = 0; j < n; j++) {if((state & (1<<j)) && a[i][j] == 1) {new_state ^= (1<<j);  // 灯是开的且可以关闭}if(!(state & (1<<j)) && a[i][j] == -1) {new_state ^= (1<<j);  // 灯是关的且可以打开}}// 更新dp值dp[new_state] = min(dp[new_state], dp[state] + 1);}}}printf("%d\n", dp[0] == INF ? -1 : dp[0]);return 0;
}

一些额外的练习

CF11D简单的任务

P3869宝藏

P4363一双木棋

总结

状态压缩dp的核心思路就是把一行数据用一串01码储存,通过位运算调用数据,需要使用者有比较好的循环体意识和位运算熟悉度,常与BFS,DFS等算法一同出现,难度较大。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Chrome Extension】一、CSDN计时扩展设计

【Chrome Extension】一、CSDN计时扩展设计 重点内容内容脚本 content_scripts 文件目录1、整体目录2、manifest.json3、scripts/content.js4、css/content.css 重点内容 内容脚本 content_scripts 1、manifest.json文件配置 {"manifest_version": 3, # *依赖Chro…

C中设计不允许继承的类的实现方法是什么?

在C中&#xff0c;设计不允许继承的类可以通过多种方法实现。以下是详细的方法说明及示例&#xff1a; ### 方法一&#xff1a;将构造函数和析构函数设为私有 这种方法的核心思想是通过将构造函数和析构函数设为私有&#xff0c;使得子类无法调用这些函数&#xff0c;从而无法…

javaEE-线程的常用方法-4

目录 一.start():启动一个线程 调用start()方法 start()方法只能调用一次&#xff1a; java中的API: start()和run()的区别: 二.中断一个线程 中断线程方法1:引入标志位 中断线程方法2:调⽤interrupt()⽅法 抛出的异常: 三.等待一个线程 join() 四、获取线程引用 五…

AI的进阶之路:从机器学习到深度学习的演变(四)

AI的进阶之路&#xff1a;从机器学习到深度学习的演变&#xff08;三&#xff09; 五、深度学习的应用领域 深度学习的应用领域广泛&#xff0c;涵盖了计算机视觉、自然语言处理、语音识别和推荐系统等多个方面。以下将详细探讨这些关键应用领域&#xff0c;展示深度学习在不同…

Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版)

Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版) 文章目录 Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版)一.环境准备1.服务器准备2.环境配置3.设置主机名4.修改国内镜像源地址5.配置时间同步6.配置内核转发及网桥过滤二.容器运行时Containerd安装(所有节点)…

dockerfile文档编写(3):构建失败后清理缓存(删除容器和镜像相关命令)

目录 删除所有强制删除所有容器强制删除所有镜像 有的时候想要修改项目&#xff0c;发现说空间不够了&#xff0c;那就需要清理一下docker存储空间了 删除所有 如果没有什么其他的项目的话&#xff0c;比较快捷和方便 强制删除所有容器 docker rm -f $(docker ps -aq)强制删…

圣诞快乐(h5 css js(圣诞树))

一&#xff0c;整体设计思路 圣诞树h5&#xff08;简易&#xff09; 1.页面布局与样式&#xff1a; 页面使用了全屏的黑色背景&#xff0c;中央显示圣诞树&#xff0c;树形由三层绿色的三角形组成&#xff0c;每一层的大小逐渐变小。树干是一个棕色的矩形&#xff0c;位于三角…

PostgreSQL和Postgis安装

Windows下PostgreSQL和对应的版本的Postgis安装 PostgreSQL安装 1、官网下载地址 https://www.enterprisedb.com/downloads/postgres-postgresql-downloads 2、根据自己的系统下载完成&#xff0c;Windows下可以直接傻瓜式安装就OK 建议不要通过自带的这个程序安装postgis,…

拒绝 Helm? 如何在 K8s 上部署 KRaft 模式 Kafka 集群?

首发&#xff1a;运维有术 今天分享的主题是&#xff1a;不使用 Helm、Operator&#xff0c;如何在 K8s 集群上手工部署一个开启 SASL 认证的 KRaft 模式的 Kafka 集群&#xff1f; 本文&#xff0c;我将为您提供一份全面的实战指南&#xff0c;逐步引导您完成以下关键任务&a…

面向微服务的Spring Cloud Gateway的集成解决方案:用户登录认证与访问控制

&#x1f3af;导读&#xff1a;本文档详细描述了一个基于Spring Cloud Gateway的微服务网关及Admin服务的实现。网关通过定义路由规则&#xff0c;利用负载均衡将请求转发至不同的后端服务&#xff0c;并集成了Token验证过滤器以确保API的安全访问&#xff0c;同时支持白名单路…

牛客网 SQL36查找后排序

SQL36查找后排序 select device_id,age from user_profile order by age asc #select [字段1,字段2] from [表名] order by [字段1] [升序(asc)/降序(desc)],[字段2] [升序(asc)/降序(desc)] #select&#xff1a;查询 #order by 排序 每日问题 如何实现对象的克隆&#xff1…

备忘一个FDBatchMove数据转存的问题

使用FDBatchMove的SQL导入excel表到sql表&#xff0c;设置条件时一头雾水&#xff0c;函数不遵守sql的规则。 比如替换字段的TAB键值为空&#xff0c;replace(字段名,char(9),)竟然提示错误&#xff0c;百思不得其解。 试遍了几乎所有的函数&#xff0c;竟然是chr(9)。 这个…

C++的封装(十四):《设计模式》这本书

很多C学习者学到对C语言有一定自信后&#xff0c;会去读一下《设计模式》这本书。希望能够提升自己的设计水平。 据我所知&#xff0c;围绕C语言出了很多书。因为正好赶上泡沫经济时代。大家一拥而上&#xff0c;自己半懂不懂就出书&#xff0c;抢着出书收割读者&#xff0c;出…

TransmittableThreadLocal线程变量传递问题

ThreadLocal有时挺好用的&#xff0c;但是在某些情况下需要将父线程中的本地变量传递给子线程时&#xff0c;这个就不行了。 那么TransmittableThreadLocal就是为了解决这个问题的&#xff0c;但是&#xff0c;如果在某些异步的场景中&#xff0c;特别是异步线程是下载文件等耗…

浅析InnoDB引擎架构(已完结)

大家好&#xff0c;我是此林。 今天来介绍下InnoDB底层架构。 1. 磁盘架构 我们所有的数据库文件都保存在 /var/lib/mysql目录下。 由于我这边是docker部署的mysql&#xff0c;用如下命令查看mysql数据挂载。 docker inspect mysql-master 如下图&#xff0c;目前只有一个数…

Ajax中的axios

既然提到Ajax&#xff0c;那就先来说一说什么是Ajax吧 关于Ajax Ajax的定义 Asynchronous JavaScript And XML&#xff1a;异步的JavaScript和XML。 反正就是一句话总结&#xff1a; 使用XML HttpRequest 对象与服务器进行通讯。 AJAX 是一种在无需重新加载整个网页的情况下&…

苹果手机怎么清理空间:拯救你的拥挤手机

在数字生活的海洋中&#xff0c;我们的苹果手机就像一艘小船&#xff0c;载满了照片、应用、视频和各种下载的“宝贝”。随着时间的推移&#xff0c;这艘小船开始变得拥挤&#xff0c;航行速度放缓&#xff0c;甚至有时候直接卡壳。苹果手机怎么清理空间&#xff1f;是时候学会…

electron-vite打包后图标不生效问题

在electron-builder.yml中&#xff0c;通过icon配置自己的图标&#xff0c;以下是正确代码 win:executableName: 名称icon: build/icon.ico nsis:artifactName: ${name}-${version}.${ext}shortcutName: ${productName}uninstallDisplayName: ${productName}createDesktopShor…

redis离线安装脚本

redis离线安装脚本 说明脚本使用完整脚本脚本内容说明1、参数校验2、文件及文件夹检查3、检查是否有同名服务4、解压、编译安装5、修改配置文件6、配置服务及开机自启动 说明 经常装服务器环境&#xff0c;根据以前的安装经验写了个安装脚本。本人不是专业运维&#xff0c;也是…

Linux - rpm yum 工具及命令总结

RPM 概述 定义&#xff1a;RPM&#xff08;RedHat Package Manager&#xff09;&#xff0c;是一个功能强大的软件包管理系统&#xff0c;用于在 Linux 系统中安装、升级和管理软件包采用系统&#xff1a;主要用于基于 RPM 的 Linux 发行版&#xff0c;如 Red Hat、CentOS、S…