插头dp-程序员宅基地

技术标签: 算法笔记  

插头dp

之前在讲状压dp时还没有了解过插头dp,但状压的最后一道例题已经和插头dp很像了,这节就来解决一个插头dp的入门问题。
插头dp学习之前需要掌握或了解的东西:状压dp,哈希表,状压用来压缩一些局面,但可能出现的情况很多导致状压后的数很大,这时候就可以使用哈希表来仅存放已出现的局面,避免开辟太大的数组。
插头dp有几个关键词,当关键词出现时可以向这方面考虑:超小数据范围,网格图,连通性。
还需要理解一些抽象的定义:
插头
一个格子以某种方式向另一个格子相连,相连的位置叫插头,具体的类似拼图的凹槽与凸出这就是一种插头。
轮廓线
我们选取一个格子(i,j)作为决策点时,图中黄线就是轮廓线
在这里插入图片描述
轮廓线可以记录影响当前决策格或以后决策格做决策的要素(插头状态)。

例题:URAL1519

题目大意:一个网格图中有若干障碍格子,求其他格子的哈密尔顿回路总数

题解
这道题的数据范围很小可以使用状态压缩,又要求连通性那就使用插头dp。
首先定义轮廓线设轮廓线的单位部分从左到右有a,b,c,d四个插头,我们发现如果a,c连通,那么b,d就不可能连通,这其实是一个括号匹配问题,于是我们可以进一步定义,如果a,b连通左边的a记为1,右边的b记为2。
这样每个轮廓线的单位部分就有了三种情况,三进制数不好运算我们可以使用四进制数记录。
16位的四进制数太大了我们需要一个方法减少一些空间使用,这就用到了哈希表通过哈希表我们可以只记录出现了的状态,其中大量的无意义的状态就不用储存了。
综上我们确定了我们的用于状态转移的状态 f ( i , j , S ) f(i,j,S) f(i,j,S)其中S为轮廓线状态。

状态转移方程

mp数组是网格的状态,0为有障碍,1为无障碍。
每次转移我们需要将决策格的上,左边转移为下,右边。其中决策格的上,左边会影响到我们当前决策格的决策,为了方便我们将其记为b1,b2,设轮廓线为a0a1a2a3a4
在这里插入图片描述
1.当前决策格有障碍
那么只有这个格子没有连通的时候才是合法情况,转移后轮廓线状态不变。

if(mp[i][j]){
    if(!b1&&!b2) ins(zt,num);}

其中ins()函数是向轮廓线状态zt的方案加上num(就是简化的 d p [ n o w ] [ z t ] + = d p [ l a s ] [ z t ‘ ] dp[now][zt]+=dp[las][zt`] dp[now][zt]+=dp[las][zt])。
2.当前决策无障碍:
(1)b1,b2都为0
那么只有决策格只有一种情况如图
在这里插入图片描述
转移后ai-1=1,ai=2。

else if (!b1 && !b2)
	{
    if (mp[i + 1][j] && mp[i][j + 1]) ins(zt + bin[j - 1] + 2 * bin[j], num);}

这里即使不判断要连通的格子是不是有障碍,也可以正确给出答案,但是会额外在哈希表中创立许多无效方案,加大了时间复杂度与空间复杂度,所以这里选择在转移前多判断一下。
(2)b1与b2其中一个为0
b2=0时有两张情况,第一种交换ai-1与ai
在这里插入图片描述
第二种轮廓线不变
在这里插入图片描述

if(b1&&!b2){
    
	if(mp[i][j+1]){
    ins(zt-bit[j-1]*b1+bit[j]*b1,num);}
	if(mp[i+1][j]){
    ins(zt,num);}
}

b1=0时同理。

if(!b1&&b2){
    
	if(mp[i][j+1]){
    ins(zt-bit[j]*b2+bit[j-1]*b2,num);}
	if(mp[i+1][j]){
    ins(zt,num);}
}

(3)b1=b2
当b1=b2=1时,当b1与b2是不同的连线可以连线,但是b1与b2可能已经连上了,所以我们需要沿着轮廓线去寻找这条线是否已经连上了。(不是单纯沿轮廓线寻找一个为2的插头,因为中间可能还有更多没连上的线)
在这里插入图片描述
转移后ai-1,ai=0,要注意的是连线后要把左边的一个2插头改为1插头。

if(b1==1&&b2==1){
    
	int k=1;
	for(int t=j+1;t<=m;t++){
    
		if((zt>>(2*t)%4)==1)k++;
		if((zt>>(2*t)%4)==2)k--;
		if(!k){
    ins(zt-bit[j]-bit[j-1]-bit[t],num);break;}
	}
}

b1=b2=2时,同理。

if(b1==2&&b2==2){
    
	int k=1;
	for(int t=j-2;t>=0;t--){
    
		if((zt>>(2*t)%4==2)k++;
		if((zt>>(2*t)%4==1)k--;
		if(!k){
    ins(zt+bit[t]-bit[j]*2-bit[j-1]*2,num);break;}
	}
}

(4)b1=2,b2=1
在这里插入图片描述
这个转移比较容易

if(b1==2&&b2==1) ins(zt-bit[j]-bit[j-1]*2,num);

(5)b1=1,b2=2
在这里插入图片描述
这种情况一定要注意如果连线就提前形成一个回路,只有最后一个非障碍的格子才可以连起来,形成回路。

完整代码

#include<iostream>
using namespace std;
const int MAX = 3e5 + 5, Has = 3e5 - 11;
int n, m, e1, e2, las, now = 0;
long long ans;
int mp[15][15], bin[15], tot[2];
int h[MAX];

struct Hashtable//哈希表,a用来存轮廓线状态,js用来存方案数,next存放哈希冲突时下一个格子的坐标
{
    
	long long js[2];
	int a[2], next;
}has[MAX];

void ins(int zt, int num)
{
    
	int tmp = zt % Has + 1;
	for (int i = h[tmp]; i; i = has[i].next)
		if (has[i].a[now] = zt) {
     has[i].js[now] += num; return; }
	has[++tot[now]].next = h[tmp];
	h[tmp] = tot[now];
	has[tot[now]].js[now] = num;
	has[tot[now]].a[now] = zt;
}

void work()
{
    
	tot[now] = 1; has[1].a[now] = 0; has[1].js[now] = 1;
	for (int i = 1; i <= n; i++)
	{
    
		for (int j = 1; j <= tot[now]; j++)has[j].a[now] <<= 2;//换行转移
		for (int j = 1; j <= m; j++)
		{
    
			las = now; now ^= 1;
			memset(h, 0, sizeof(h));
			tot[now] = 0;
			for (int k = 1; k <= tot[las]; k++)
			{
    
				int zt = has[k].a[las];
				long long num = has[k].js[las];
				int b1 = (zt >> (2 * (j - 1))) % 4, b2 = (zt >> (2 * j)) % 4;//提取决策格上的轮廓线
				if (!mp[i][j]) {
     if (!b1 && !b2)ins(zt, num); }
				else if (!b1 && !b2)
				{
    
					if (mp[i + 1][j] && mp[i][j + 1]) ins(zt + bin[j - 1] + 2 * bin[j], num);
				}
				else if (!b1&&b2) {
    
					if (mp[i][j + 1]) ins(zt, num);
					if (mp[i + 1][j]) ins(zt - bin[j] * b2 + bin[j - 1] * b2, num);
				}
				else if (b1 && !b2) {
    
					if (mp[i][j + 1]) ins(zt - bin[j - 1] * b1 + bin[j] * b1, num);
					if (mp[i + 1][j]) ins(zt, num);
				}
				else if (b1 == 1 && b2 == 1) {
    
					int kl = 1;
					for (int t = j + 1; t <= m; ++t) {
    
						if ((zt >> (t * 2)) % 4 == 1) ++kl;
						if ((zt >> (t * 2)) % 4 == 2) --kl;
						if (!kl) {
     ins(zt - bin[j] - bin[j - 1] - bin[t], num); break; }
					}
				}
				else if (b1 == 2 && b2 == 2) {
    
					int kl = 1;
					for (int t = j - 2; t >= 0; --t) {
    
						if ((zt >> (t * 2)) % 4 == 1) --kl;
						if ((zt >> (t * 2)) % 4 == 2) ++kl;
						if (!kl) {
     ins(zt + bin[t] - 2 * bin[j] - 2 * bin[j - 1], num); break; }
					}
				}
				else if (b1 == 2 && b2 == 1) ins(zt - 2 * bin[j - 1] - bin[j], num);
				else if (i == e1 && j == e2) ans += num;
			}
		}
	}
}

int main()
{
    
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
    
		for (int j = 1; j <= m; j++)
		{
    
			char ch;
			ch = getchar();
			while (ch != '*'&&ch != '.')ch = getchar();
			if (ch == '.')
			{
    
				mp[i][j] = 1;
				e1 = i;
				e2 = j;
			}
		}
	}
	bin[0] = 1;
	for (int i = 1; i <= 12; i++)bin[i] = bin[i - 1] << 2;
	work();
	cout << ans << endl;
	system("pause");
	return 0;
}

本题除了转移方程的策略选择,还有哈希表的构造,本题哈希需要存储轮廓线状态,方案数两种数据各两次,这里使用了一个结构体包装,如果拆成单个数组也可以通过相同的索引调用数据。
本题的哈希表处理哈希冲突的方式是链表法,将冲突的元素通过next连接到了链表头。
这里的代码我发现与储存图的链式向前星相似(都是用数组写链表)可以结合理解。
还有本题用到了滚动数组减少空间,之前状压dp也讲到了。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/shi_zi_183/article/details/108579692

智能推荐

汉字乱码解决方法_中文动态链接库被英文动态链接库覆盖所造成的-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏9次。汉字乱码解决方法 www.xyhhxx.com 发布者: seo 时间: 2005-09-12 我们在使用电脑时,经常会遇到乱码:例如登上港台网站时会看到乱码,打开E-mail时也会看到乱码,更为严重的是原先显示正常的Win9X/Win2K桌面、菜单中汉字一夜之间“面目全非”,本来显示正常的各种应用程序(包括游戏)中汉字也成了乱码!乱码给我们带来了太多的烦恼,告别乱码是我们共同的愿望! 一、汉字乱_中文动态链接库被英文动态链接库覆盖所造成的

PDF Drive-程序员宅基地

文章浏览阅读1.5w次。PDF Drive是一个免费的搜索引擎,允许您搜索,预览和下载数百万个PDF文件到您的设备。我们的抓取工具不断扫描万维网,将PDF文件添加到我们的数据库中。如果PDF文件从网络中撤回,则它们也会立即从PDF Drive搜索结果中撤消 。通过这种方式,我们的PDF Drive库保持最新,同时不断发展并为您提供庞大的搜索数据库。除了传统的搜索引擎,PDF Drive还具有以下额外功能:预览所有文件..._pdf drive

Android11编译第五弹:开启VPN权限_安卓虚拟网络权限-程序员宅基地

文章浏览阅读1.9k次。虚拟专用网(VPN)是一条通信隧道,可以在不可信的中间网络上提供身份认证和数据通信的点对点传输。大多数VPN使用加密技术来保护封装的通信数据,但是加密对于VPN 连接而言并非必需的。简单来说,设备不论连接什么类型的网络,只要和VPN服务器提供的网络,那么这些设备就在VPN网络中,相当于在同一个虚拟局域网内。因此就可以使用adb访问智能货柜设备。因为需要支持VPN访问,因此AOSP需要定制支持VPN权限。_安卓虚拟网络权限

【VS配置】如何设置调试命令行参数_vs 调试 命令行参数-程序员宅基地

文章浏览阅读5k次。右键项目->属性->配置属性->调试,如下图命令:即是应用程序的绝对路径命令行参数自行设定以下设置是我调试 Nvidia编码设置的参数_vs 调试 命令行参数

数字后端基本认识-程序员宅基地

文章浏览阅读2.3w次,点赞50次,收藏402次。1、数字后端的目的传统上将布局布线前的工作称之为数字前端(Front End)设计,而将布局布线之后的工作称为数字后端(Back End)设计。布局的目的在于产生制作掩膜所需的GDSII文件。同时也产生布局后的网表文件(Netlist)及标准延迟文件(SDF)。2、数字芯片后端工程师要做什么主要工作就是接收数字前端提交的代码,最终交付一个完整的芯片布局布线结果。工作职责(1)从事SoC物理实现(P&R)工作,包括版图设计(floorplan)与后端验证(LVS/DRC)等(2._数字后端

计算机基础知识面试题集_计算机基础面试题-程序员宅基地

文章浏览阅读6.3w次,点赞100次,收藏955次。凡是计算机类的面试都少不了计算机基础知识,汇总整理此类知识有助于面试集中复习,说不定什么时候就用上了。1、ICMP 是什么协议?处于哪一层?答:ICMP是(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。属于网络层协议控制消息是指网络通不通、主机..._计算机基础面试题

随便推点

layui框架中switch 开关监听+ajax 数据更新案例_layui-form-switch 触发ajax事件-程序员宅基地

文章浏览阅读1.3w次。layui.use('form', function(){ var form = layui.form ,layer =layui.layer; //监听短信开关 form.on('switch(alert_sms)', function(data){ var index_sms; var alert_value =this.checked ? '1'_layui-form-switch 触发ajax事件

Numpy:repeat用法详解 Python_python np.repeat-程序员宅基地

文章浏览阅读811次。NumPy的函数是一个非常有用的函数,可以用来重复数组中的元素。本文详细介绍了函数的用法,包括扁平化重复操作和按轴重复操作。我们还提供了相应的源代码示例,希望能帮助读者更好地理解和使用函数。_python np.repeat

Mac for postman interceptor安装_mac postman interceptor-程序员宅基地

文章浏览阅读1k次。1、先安装chrome浏览器下载一个chrome,进行正常安装即可,本人chrome版本为92,下载插件一定要最新版本1.1以上;2、再安装postman从官网下载了一个新最的进行正常安装(本人的太老了,取了最新版本postman)3、在chrome中添加插件interceptor下载地址:https://www.crx4chrome.com/crx/560/下载文件名为aicmkgpgakddgnaphhhpliifpcfhicfo-1.1.2-Crx4Chrome.c.._mac postman interceptor

分布式锁,使用redis还是zookeeper?--中篇_zoomkeeper 与redis-程序员宅基地

文章浏览阅读263次。前言上篇已经详细提及到redis实现的redLock算法下的分布式锁,在项目里出现的问题以及提出的解决的方案,现在就针对这个分布式锁的话题,这节就针对zk的锁来详细说明,项目里使用的zk实现分布式锁还是很方便的,使用起来比redis要高效,安全。而且ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件..._zoomkeeper 与redis

linux0.01源代码分析笔记_linux0.01源码分析-程序员宅基地

文章浏览阅读7.6k次,点赞6次,收藏45次。linux0.01(原始版)源代码分析笔记 1. 整体结构:第一个文件夹boot ,包含boot.s 和head.s 。boot.s 实现计算机加电自检引导扇区,第一次加载扇区和第二次加载操作系统的功能,head.s 主要包括初始设置的代码、时钟中断int 0x08的过程代码、系统调用中断int 0x80的过程代码以及任务A 和任务B 等的代码和数据。 (其中.S为扩展名的文件为汇编文件..._linux0.01源码分析

CAS-KG——知识推理_部分完整性假设-程序员宅基地

文章浏览阅读1.4k次,点赞2次,收藏19次。说明:CAS是国科大的简称,KG是知识图谱的缩写,这个栏目之下是我整理的国科大学习到的知识图谱的相关笔记。课程目标了解以知识图谱为代表的大数据知识工程的基本问题和方法掌握基于知识图谱的语义计算关键技术具备建立小型知识图谱并据此进行数据分析应用的能力教学安排详情请见博客:CAS-KG——课程安排文章目录..._部分完整性假设