插头DP(基于连通性状态压缩的动态规划问题)(让你从入门到绝望)
今天,我,Monkey king 又为大家带来大(ju)佬(ruo)的算法啦!——插头DP
例题(菜OJ上的网址:http://caioj.cn/problem.php?id=1489):
那么,这道题怎么做呢?(虽然菜OJ上有视频)
插头DP能完美解决!
注:我采用的是括号表示法(一个神奇的、猛如虎的神奇表示法)
首先,我们先讲一下插头,总共6种双插头(一般用来解决回路问题和辅助单插头完成路径问题)和不知多少种单插头(用来解决路径问题)
别看插头多,其实大部分相同!
插头:
。。。。。。拿错了
画得好丑
注:这只画了6种单插头,因为单插头在不同情况下可能会分(泌)出不同的情况,所以单插头难度比较大。
现在讲一下轮廓线:
那红不溜秋的东西十分重要!
如图,一条回路如同一大堆插头拼在一起!
括号表示法就是把轮廓线的状态用括号表示,从而压缩状态!
因为每一块(连在一起的插头)插头左右的要去扩张的插头会一直往下扩,所以不会出现左插头飞右插头右边
如:
所以,我们可以把左插头表示为左括号,右插头为右括号(一对),插头中的一部分或没插头为空。
但是,状态压缩呀,总不可能开个字符吧!那么,把左插头表示成1,右插头为2,空为0,每个数用两个二进制表示:01,10,00
然后,从左让右将二进制数合成一个大的数,用来表示当前状态!
如:
因为要包括没搞定的格子,所以轮廓线长度为m(矩阵宽度)+1
那么为什么要用二进制呢?位运算!
取出第轮廓线上的第q个位置的数:
int set(int s,int p)
{
return (s>>((p-1)*2))&3;
}
改变第q位上的数为v:
void change(int &s/*引用,别打漏了*/,int p,int v)
{
s^=set(s,p)<<((p-1)*2);
s^=v<<((p-1)*2);
}
不理解的同学搜一下C++的位运算理解一下!
其实,插头讲究的是分类讨论,对每种插头情况分类讨论!
先将Hash表,定一个inf和几个数组
定x,y,z,k分别%inf后得1 4 5 3
那么,得:
代码如下:
struct node
{
int hash[mod]/*压状态*/,key[mod]/*记录原本的数值*/,size/*记录存了多少数*/;ll num[mod]/*记录当前数值代表了多少状态*/;
void mem()
{
memset(hash,-1,sizeof(hash));size=0;//初始化函数
}
void add(int S,ll sum)
{
int s=S%mod;
while(hash[s]!=-1 && key[hash[s]]!=S)
{
s++;s%=mod;
}//判断重复,这样做是可以保证下次扔一个同样的数也可以到这个hash值
if(hash[s]==-1)
{
hash[s]=++size;key[size]=S;num[size]=sum;
}//新建一个hash格
else num[hash[s]]+=sum;//有的话直接加
}
}dp[2];//滚动数组
有人会问:为什么同样的数值表示不用状态的方案数是可以加在一起的呢?
因为:
不管下面组成怎样,他们都可以接受,所以,他们的虽然样子不同,但状态相同,我们就可以把他们归为一类。
那么,接下来最难的其实是分类讨论,插头最难的就是因为它难调且容易漏了几种情况。
现在,我们设现在准备安上插头的格子从左面来的插头为q,上面来的为p。
如:
那么,我们就要利用q和p来分类讨论。。。就是代码。。。就不要在意了(一百多行)。
现在到转移状态了。
以这图为例:
因为这是一个障碍,所以只有当q=0并且p=0可以继承状态。
来个没障碍的:
那么,再讲两种比较难想的。
这种:
插头的概念就是可以延伸的插头一定会去延伸或和其他插头结合,但是什么时候结束呢?
这道题而言:
就是当q=1并且p=2时且已经到最后一个非障碍格子时就可以将当前的状态记入状态。
但是!q=1并且p=2的情况不在最后一个非障碍格子时,就算一个废的情况(提前生成回路)。
为什么q=1并且p=2的情况一定生成回路呢?
如图:
那么最后一个格会不会不会出现一对的情况?(没有单插头)
只要有合法情况出现,因为插头的概念就是可以延伸的插头一定会去延伸或和其他插头结合,所以两个插头会一直延伸到最后一个格相遇!
就结束了。
注意这道题要判断全是障碍的情况。
上代码!
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;//不知道叫什么,感觉很大佬,就学了^_^
const int mod=200000;//hash表大小,因为hash表有压缩功能,所以一般100万就够了。
int map[50][50],n,m,ex=-1,ey=-1;//最后一个障碍格子的坐标
char ss[210];
ll ans=0;
struct node
{
int hash[mod],key[mod],size;ll num[mod];
void mem()
{
memset(hash,-1,sizeof(hash));size=0;
}//初始化
void add(int S,ll sum)
{
int s=S%mod;
while(hash[s]!=-1 && key[hash[s]]!=S)
{
s++;s%=mod;
}//找格子
if(hash[s]==-1)
{
hash[s]=++size;key[size]=S;num[size]=sum;
}
else num[hash[s]]+=sum;//将方案记录
}
}dp[2];
int now,php;
int set(int s,int p)
{
return (s>>((p-1)*2))&3;
}//取出第k个数
void change(int &s,int p,int v)
{
s^=set(s,p)<<((p-1)*2);
s^=(v&3)<<((p-1)*2);
}//改变
void work()
{
ll sum=0;
now=0;php=1;
dp[now].mem();
dp[now].add(0,1);//初始化滚动型DP数组
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
swap(now,php);
dp[now].mem();//滚动(dan)数组
for(int k=1;k<=dp[php].size;k++)
{
int s=dp[php].key[k];
sum=dp[php].num[k];
int q=set(s,j);
int p=set(s,j+1);
if(map[i][j]==0)
{
if(q==0 && p==0)dp[now].add(s,sum);
continue;
}//障碍格子
if(q==0 && p==0)
{
if(map[i+1][j]==1 && map[i][j+1]==1)//判断是否有障碍
{
change(s,j,1);change(s,j+1,2);//从这个格子建两个插头
dp[now].add(s,sum);
}
}//没人指我,只好自己建一个插头
else if(q>0 && p==0)
{
if(map[i+1][j]==1)dp[now].add(s,sum);//其实你拆开后简化就是这样子的
if(map[i][j+1]==1)//将插头引入右边,继承下去
{
change(s,j,0);change(s,j+1,q);
dp[now].add(s,sum);
}
}//继承
else if(q==0 && p>0)
{
if(map[i][j+1]==1)dp[now].add(s,sum);
if(map[i+1][j]==1)//将插头引入下边,继承下去
{
change(s,j,p);change(s,j+1,0);
dp[now].add(s,sum);
}
}//继承
else if(q==1 && p==1)
{
int find=1;//算上p为1!很重要。
for(int tt=j+2;tt<=m;tt++)//找到与p相对的右插头并修改为这一大块插头的左插头
{
int vs=set(s,tt);
if(vs==1)find++;
else if(vs==2)find--;//为什么可以?因为中间罩住的插头不会出去这个大插头的范围,所以只有碰到与p成对的插头才会清零
if(find==0)
{
change(s,j,0);change(s,j+1,0);change(s,tt,1);
dp[now].add(s,sum);
break;//你不加这个你试试
}
}
}
else if(q==2 && p==2)
{
int find=1;
for(int tt=j-1;tt>=1;tt--)//找到与q相对的左插头并修改为这一大块插头的右插头
{
int vs=set(s,tt);
if(vs==2)find++;
else if(vs==1)find--;//这样是不是太啰嗦了?
if(find==0)
{
change(s,j,0);change(s,j+1,0);change(s,tt,2);
dp[now].add(s,sum);
break;
}
}
}
else if(q==2 && p==1)//呵呵,这样的状态十分好想!因为对应的插头刚好也是我们想要的,只要把当前的q和p连接就好了!
{
change(s,j,0);change(s,j+1,0);
dp[now].add(s,sum);
}
else if(q==1 && p==2)
{
if(ex==i && ey==j)ans+=sum;//得到答案
}
}
}
for(int j=1;j<=dp[now].size;j++)dp[now].key[j]<<=2;//看下面解释
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%s",ss+1);
for(int j=1;j<=m;j++)
{
if(ss[j]=='.'){map[i][j]=1;ex=i;ey=j;}//这样做方便后面的判断
}
}
if(ex==-1){printf("0\n");return 0;}//特判
work();
printf("%lld\n",ans);//注意答案到了long long
return 0;
}
在代码中只继承了合法方案,不合法方案已经被过滤了
估计许多人不理解这段
else if(q==2 && p==1)
{
change(s,j,0);change(s,j+1,0);
dp[now].add(s,sum);
}
如图:
还有这段
for(int j=1;j<=dp[now].size;j++)dp[now].key[j]<<=2;
因为每次完成一层,轮廓线都要下降一层,如:
(红色是轮廓线)
因为代码中判断矩阵外的格子为障碍,所以轮廓线最后那条竖线代表的是0,而且从下一行开始,一开始也没有插头指向开头那条竖线(总不可能在矩阵外开插头吧!),也为0,刚好每个状态(除了那条竖线外)也往后一位,所以,我们就把二进制往右移两位(因为每个括号要两个二进制数表示)来表示一层向下一层的转移!
那么,插头DP就解决啦!
注:上面的图片侵权抱歉!
插头DP(基于连通性状态压缩的动态规划问题)(让你从入门到绝望)的更多相关文章
- caioj1496: [视频]基于连通性状态压缩的动态规划问题:Manhattan Wiring
%%%%orz苏大佬 虽然苏大佬的baff吸不得,苏大佬的梦信不得,但是膜苏大佬是少不得的囧 这题还是比较有收获的 哼居然有我不会做的插头DP 自己yy了下,2表示属于2的插头,3表示3的插头 假如当 ...
- caioj1495: [视频]基于连通性状态压缩的动态规划问题:Formula 2
本来想写一天插头的,但是这题太难受(绝望)500+的代码量..我选择下午放松一下. 先ORZ一下苏大佬(yz的cdq啊%%%%%)居然把cdq论文里面的题抠出来出数据放在c站(呵呵真是个悲伤的故事不过 ...
- 【BZOJ2734】【HNOI2012】集合选数(状态压缩,动态规划)
[BZOJ2734][HNOI2012]集合选数(状态压缩,动态规划) 题面 Description <集合论与图论>这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所 ...
- DP大作战—状态压缩dp
题目描述 阿姆斯特朗回旋加速式阿姆斯特朗炮是一种非常厉害的武器,这种武器可以毁灭自身同行同列两个单位范围内的所有其他单位(其实就是十字型),听起来比红警里面的法国巨炮可是厉害多了.现在,零崎要在地图上 ...
- 【NOIP2017】宝藏(状态压缩,动态规划)
[NOIP2017]宝藏(状态压缩,动态规划) 题面 洛谷 题目描述 参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋, 也给出了这 n 个宝藏屋之间可供开发的 m 条道路 ...
- 洛谷P2258 子矩阵 题解 状态压缩/枚举/动态规划
作者:zifeiy 标签:状态压缩.枚举.动态规划 题目链接:https://www.luogu.org/problem/P2258 这道题目状态压缩是肯定的,我们需要用二进制来枚举状态. 江湖上有一 ...
- HOJ-2662Pieces Assignment(状态压缩,动态规划)
Pieces Assignment Source : zhouguyue Time limit : 1 sec Memory limit : 64 M Submitted : 415, Accepte ...
- 【NOI2001】炮兵阵地(状态压缩,动态规划)
题面 题面中有图片的存在,所以就贴个地址把 题解 简单题,,,, 原来一直觉得不会做... 现在发现是一道傻逼题 暴力压两行的状态 发现就需要滚一维. 然后暴力检查一下状态的可行性 DP检查MAX就可 ...
- HDU-4539郑厂长系列故事——排兵布阵(状态压缩,动态规划)
郑厂长系列故事--排兵布阵 Time Limit : 10000/5000ms (Java/Other) Memory Limit : 65535/32768K (Java/Other) Total ...
随机推荐
- Siebel Tools client安装假死在92%问题解决
Solution to this error: This issue happens incase of installation is on unsupported operating system ...
- [Err] 1214 - The used table type doesn't support FULLTEXT indexes
-- -- Table structure for table `film_text` -- -- InnoDB added FULLTEXT support in 5.6.10. If you us ...
- Laravel 生成小程序图文海报最佳方案之一
目前已经更新 2.0 版本,支持生成的海报关联Model,支持是否重新生成海报等功能,具体更新请移步 github: laravel-miniprogram-poster 微信小程序官方并未提供分享到 ...
- 简单记录一下http请求的7个步骤
1.建立TCP连接 2.客户端发送请求命令 3.客户端发送请求头信息 4.服务端应答请求,返回版本号和状态码 5.服务端应答头信息 6.服务端向客户端发送数据 7.服务器关闭TCP连接(Connect ...
- expdp指定时间戳导出防止数据不一致
expdp scott/tiger DIRECTORY=dump DUMPFILE=a.dmp FLASHBACK_TIME=“TO_TIMESTAMP(’25-08-2004 14:35:00’,’ ...
- mysqldump备份脚本一例
参考三思老师书中所写,感觉挺好用,记录下来,虽然是抄袭,但是手抄还是很累的,其中用到的其他脚本,在博客中已经记录: mysql_full_backup.sh#!/bin/sh#Created by C ...
- simple2.py
#coding: utf-8 import xlsxwriter workbook = xlsxwriter.Workbook('chart.xlsx') worksheet = workbook.a ...
- CRUD全栈式编程架构之服务层的设计
服务层代码 首先我先放出2个主要类的代码再分别讲解 接口 using System; using System.Collections.Generic; using System.Linq; usin ...
- Fiddler实现IOS手机抓取https报文
如何设置代理访问内网进而抓取手机的Https报文进行分析定位. 准备工作: 1.PC上连接好VPN 2.管理员方式打开Fiddler工具 开搞: 一.设置Fiddler 1.打开Tools->O ...
- Android(java)学习笔记30:泛型接口的概述和使用
1. 泛型接口的概述和使用: package cn.itcast_06; /* * 泛型接口:把泛型定义在接口上 */ public interface Inter<T> { public ...