首页 > 其他分享 >状态压缩DP

状态压缩DP

时间:2024-12-14 20:55:05浏览次数:6  
标签:状态 int 压缩 一行 dep 枚举 ll DP

  • 不得不说,状压DP是我比数位DP还要烂的板块。尽管其代码很短,但每次写的仍然漏洞百出,要调很久,还是太不熟练了。

  • 以前一直搞不清楚状压与数位的区别,现在大概知道了:数位更专注于“数”,而状压只是一般而言将数据转化为二进制(当然也有三进制之类的神秘做法)方便转移及处理。

P1896 [SCOI2005] 互不侵犯

互不侵犯一直是比较经典的状压DP模板题。

  • 显然对于某一行,其状态只由上一行所限制,也就是放的“王”不能互吃
    同时,数据范围极小(这也是状压的一个特点),足够我们去枚举每一行放的情况。

  • 那如何去表示状态呢?这里就体现出“状态压缩”的含义了。我们设1 表示放了王 0表示没放,这样我们就可以方便地通过这种方式用一个数字去表示状态了。

  • 0001 0101就是一个状态,只是在计算中被压缩成了21这个数而已。
    这时DP状态就比较显然了。设 \(f_{h,lst,num}\) 表示当前统计到第 \(h\) 行,上一行的状态是 \(lst\),已经放了 \(num\) 个棋子的方案数。

  • 而转移方式就比较显然了。由于时间上允许直接枚举状态,因此对于每个枚举的状态,都去判断与上一行是否满足条件,相符就继续搜,不相符就跳了。
    由于我们是用二进制去维护的,我们可以方便地使用位运算左右移位去判断是否满足条件。
    当我们枚举到第 \(n+1\) 行时,就代表前面的 \(n\) 行都算完了,因此直接判断是否刚好使用了 \(k\) 个棋子就行了,如果是就答案加一。

  • 这里使用的是记忆化搜索的写法。事实上记忆化搜索是非常方便的DP实现方式,实现递推或者递归都比较直观、显然,可以避免循环极多层for的情况。
    事实上,状压或者数位的记忆化搜索写法一般而言都比循环好看很多(可以看下某些循环题解,及其吓人)

  • 时间复杂度就很显然了。一般而言记忆化搜索的最劣复杂度就是其空间复杂度(因为重复搜到时都记忆化完走了,真正计算次数的量级就是空间)
    因此对于此题而言,其空间时间复杂度都为 \(O(k\times n\times 2^n)\)

  • 注意当放置的国王数已经超过 \(k\) 的时候就不用搜了,可以直接返回。同时要注意同一行间放国王的限制。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll N;
ll n,k,f[12][1024][100];
ll dfs(ll last,ll h,ll sum)
{
	if(sum>k||(h==n+1&&sum!=k)) return f[h][last][sum]=0;
	if(h==n+1&&sum==k) return f[h][last][sum]=1;
	if(f[h][last][sum]!=-1) return f[h][last][sum];
	ll res=0;
	for(ll i=0;i<N;i++)
	{
		ll num=0;
		for(ll j=0;j<=n;j++) if((i>>j)&1) num++;
		if(((last&(i<<1))==0)&&((last&(i))==0)&&(((last&(i>>1)))==0)&&((i&(i>>1))==0)&&((i&(i<<1))==0))
		res+=dfs(i,h+1,num+sum);
	}
	return f[h][last][sum]=res;
}
int main()
{
	memset(f,-1,sizeof(f));
	cin>>n>>k;
	N=1<<n;
	cout<<dfs(0,1,0)<<endl;
	return 0;
}

P2704 [NOI2001] 炮兵阵地

炮兵阵地同样是典题(但硬控我快三个月),但这道题大部分都是循环写法,洛谷上前面的题解没有一篇是相对简洁易懂的记搜写法。

  • 同样与上一道题类似,只是“王”变了走法同时有地形限制,有些格子没办法放。
    显然这里对于每一行的状态就由上两行转移而来了,同时同一行的要求更长。

  • 考虑类似上一道题的状态,设同样是1 为放了炮,0 为没放,则设
    \(f_{dep,l1,l2}\) 。其中的 \(dep,l1,l2\) 分别表示已经正在统计第几行、上一行的状态、上上一行的状态。
    而其本身代表 其本身这一行以及这一行下面所有行所能塞下的最多的炮的个数

  • 为什么要这么设呢?考虑一下状压的本质,我们可以统计到这一行上方已经放置了多少个炮以及前两行的状态,
    因此我们需要一个状态来记录这个确定的状态下其本身这一行及下方所有行所可能放置的最多的炮数,这样就可以快速统计答案。
    即设其上方已经放了 \(up\) 个炮了,其下方最多放 \(down\) 个,那 \(ans\) 就是 \(\max(ans,up+down)\)

  • 很显然,不是吗?现在只需要统计 \(down\) 就可以了(\(up\) 当然是上面传下来的)
    求法就与上一题几乎完全一样了。边界同样是 \(dep=n+1\) 时,这一行什么都不能放,因此就统计一下答案然后退出了(返回0是因为这一行不能对上一行造成任何贡献)
    对于地图本身的限制,只需要先把地图的每一行同样压成二进制,枚举状态的时候判断一下有没有重叠即可。

  • 空间时间复杂度都是 \(O(n\times (2^m)^2)\)
    然后就爆完了。

  • 怎么优化一下呢?发现同行之间的限制很强,大部分的状态都没有用。
    因此考虑只枚举那些有用的,那些同行间都互斥的显然没有任何必要去枚举了。
    打一下表发现有用的甚至不到一百个,因此枚举的时候就枚举有用的序号,预先将有用的存储在数组中(注意状态中的 \(l1,l2\) 也就变成了序号而不是状态本身了)

  • 然后负责度就变成 \(O(n\times w^2)\) 了,其中 \(w\) 是有用的状态数,不到100,然后就能过了。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1023;
const int N=105;
const int M=15;
int mp[N],n,m,yu[inf],cnt=0,f[N][N][N],ans;
int dfs(int dep,int l1,int l2,int up)
{
	if(dep==n+1) {ans=max(ans,up);return 0;}
	if(f[dep][l1][l2]!=-1) return f[dep][l1][l2];
	int down=0;
	for(int i=1;i<=cnt;i++)
		if((((yu[i]&yu[l1]))||((yu[i]&yu[l2]))||((yu[i]&mp[dep]))||(yu[l1]&yu[l2]))) continue;
		else{
			int tmp=0;for(int j=0;j<m;j++) tmp+=(yu[i]>>j)&1;
			int res=dfs(dep+1,i,l1,up+tmp);
			down=max(down,tmp+res);
		}
	ans=max(ans,up+down);
	return f[dep][l1][l2]=down;
}
signed main()
{
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;char op;memset(f,-1,sizeof(f));
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>op,mp[i]<<=1,mp[i]|=(op=='H');
	for(int i=0;i<=(1<<m)-1;i++) if(!((i&(i<<1))||(i&(i<<2))||(i&(i>>1))||(i&(i>>2)))) yu[++cnt]=i;
	int tmp=dfs(1,0,0,0);
	cout<<ans<<'\n';
	return 0;
}

标签:状态,int,压缩,一行,dep,枚举,ll,DP
From: https://www.cnblogs.com/allforgod/p/18607172

相关文章

  • 事件控制块的清空与状态查询
    目录事件控制块的清空事件控制块的状态查询事件控制块的清空        将事件控制块中的所有任务从它的等待队列中移除,再将这些任务插入就绪队列。事件控制块的状态查询        仅需知道事件状态块中有多少个任务需要等待。tEvent.c#include"tinyOS......
  • WordPress教程66集(全)
    WordPress教程66集(全)6小时https://www.bilibili.com/video/BV1AG4y1Q7sV/?spm_id_from=333.337.search-card.all.click&vd_source=d10c649e248b08f4441d8cd6b76f46fcwordpressAstrahttp://www.wp.com/wp-adminyoastseoElementorWebsiteBuilderEssentialAddonsfor......
  • 机载电脑通过TypeC连接Pixhawk 6c (PX4)后的状态确认及问题解决
    在三个终端依次运行命令查看连接状态roscoeroslaunchmavrospx4.launchrostopicecho/mavros/state1.运行mavrospx4.lauch时udp1报错“networkisunreachable”原因:网关未设置解决方法:先通过使用route命令查看默认ip,发现网关未设置,再通过以下命令设置网关:sudorout......
  • DDPG算法求解月球着陆游戏
    DDPG算法求解月球着陆游戏DDPG算法,即DeepDeterministicPolicyGradient算法,是一种专门用于解决连续控制问题的深度强化学习算法。DDPG算法是由DeepMind团队提出的,用于处理连续动作空间的强化学习问题。它结合了确定性策略梯度方法和深度神经网络,旨在学习连续动作空间中的......
  • 转载:【AI系统】模型压缩基本介绍
    随着神经网络模型的复杂性和规模不断增加,模型对存储空间和计算资源的需求越来越多,使得部署和运行成本显著上升。模型压缩的目标是通过减少模型的存储空间、减少计算量或提高模型的计算效率,从而在保持模型性能的同时,降低模型部署的成本。模型压缩的目标可以概括为以下几点:减少模......
  • Unity类银河战士恶魔城学习总结(P177 Slime-stunned state improvement史莱姆眩晕状态
    【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/本章节加入了弹反使得史莱姆溶解的动画史莱姆的状态机SlimeStunState.cs1.Enter()功能:定义敌人在进入眩晕状态时的初始行为。逻辑:父类初始化:调......
  • Tomcat(75) Tomcat的集群会话状态监控
    在Tomcat集群中监控会话状态是一个重要的任务,它可以帮助你确保会话复制正常工作,并在出现问题时及时采取措施。以下是如何在Tomcat集群中监控会话状态的详细步骤,包括使用JMX(JavaManagementExtensions)和编写自定义监控脚本。1.启用JMXJMX是Java平台的一个标准,用于管理和......
  • Tomcat(76)如何在Tomcat中配置集群会话状态同步?
    在Tomcat中配置集群会话状态同步是确保应用程序高可用性和冗余的重要步骤。以下是详细的配置步骤和代码示例,以确保在Tomcat集群中实现会话状态同步。1.配置TomcatCluster首先,需要修改Tomcat的server.xml文件来配置集群和会话复制。a.编辑server.xml在Tomcat的conf/s......
  • WordPress网站修改密码,保障账户安全
    修改WordPress网站密码的步骤登录后台:使用当前的用户名和密码登录WordPress网站的后台管理界面。通常在网站的底部或顶部导航栏可以找到登录入口。进入用户设置:在后台管理界面的左侧菜单栏中,找到“用户”选项,然后选择“我的个人资料”或“编辑用户”。选择修改密码:在用户设置......
  • 网络通信与状态管理:深入理解Cookie、Session及Web工具
    前言:在当今数字化的网络世界中,Web技术的基石构建起了我们丰富多彩的互联网体验。其中,Cookie和Session犹如隐匿于幕后的关键使者,默默地在客户端与服务器之间传递着信息,管理着用户的状态与交互数据,深刻影响着我们在各类网站与应用中的每一次操作与交互流程。与此同时,Link......