今天,我,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(基于连通性状态压缩的动态规划问题)(让你从入门到绝望)的更多相关文章

  1. caioj1496: [视频]基于连通性状态压缩的 动态规划问题:Manhattan Wiring

    %%%%orz苏大佬 虽然苏大佬的baff吸不得,苏大佬的梦信不得,但是膜苏大佬是少不得的囧 这题还是比较有收获的 哼居然有我不会做的插头DP 自己yy了下,2表示属于2的插头,3表示3的插头 假如当 ...

  2. caioj1495: [视频]基于连通性状态压缩的 动态规划问题:Formula 2

    本来想写一天插头的,但是这题太难受(绝望)500+的代码量..我选择下午放松一下. 先ORZ一下苏大佬(yz的cdq啊%%%%%)居然把cdq论文里面的题抠出来出数据放在c站(呵呵真是个悲伤的故事不过 ...

  3. 【BZOJ2734】【HNOI2012】集合选数(状态压缩,动态规划)

    [BZOJ2734][HNOI2012]集合选数(状态压缩,动态规划) 题面 Description <集合论与图论>这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所 ...

  4. DP大作战—状态压缩dp

    题目描述 阿姆斯特朗回旋加速式阿姆斯特朗炮是一种非常厉害的武器,这种武器可以毁灭自身同行同列两个单位范围内的所有其他单位(其实就是十字型),听起来比红警里面的法国巨炮可是厉害多了.现在,零崎要在地图上 ...

  5. 【NOIP2017】宝藏(状态压缩,动态规划)

    [NOIP2017]宝藏(状态压缩,动态规划) 题面 洛谷 题目描述 参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋, 也给出了这 n 个宝藏屋之间可供开发的 m 条道路 ...

  6. 洛谷P2258 子矩阵 题解 状态压缩/枚举/动态规划

    作者:zifeiy 标签:状态压缩.枚举.动态规划 题目链接:https://www.luogu.org/problem/P2258 这道题目状态压缩是肯定的,我们需要用二进制来枚举状态. 江湖上有一 ...

  7. HOJ-2662Pieces Assignment(状态压缩,动态规划)

    Pieces Assignment Source : zhouguyue Time limit : 1 sec Memory limit : 64 M Submitted : 415, Accepte ...

  8. 【NOI2001】炮兵阵地(状态压缩,动态规划)

    题面 题面中有图片的存在,所以就贴个地址把 题解 简单题,,,, 原来一直觉得不会做... 现在发现是一道傻逼题 暴力压两行的状态 发现就需要滚一维. 然后暴力检查一下状态的可行性 DP检查MAX就可 ...

  9. HDU-4539郑厂长系列故事——排兵布阵(状态压缩,动态规划)

    郑厂长系列故事--排兵布阵 Time Limit : 10000/5000ms (Java/Other) Memory Limit : 65535/32768K (Java/Other) Total ...

随机推荐

  1. Java中long和Long有什么区别(转)

    Java的数据类型分两种:1.基本类型:long,int,byte,float,double,char2. 对象类型(类): Long,Integer,Byte,Float,Double,Char,S ...

  2. 【NLP_Stanford课堂】语言模型1

    一.语言模型 旨在:给一个句子或一组词计算一个联合概率 作用: 机器翻译:用以区分翻译结果的好坏 拼写校正:某一个拼错的单词是这个单词的概率更大,所以校正 语音识别:语音识别出来是这个句子的概率更大 ...

  3. Spark天堂之门解密

    本课主题 什么是 Spark 的天堂之门 Spark 天堂之门到底在那里 Spark 天堂之门源码鉴赏 引言 Spark 天堂之门就是SparkContext,这篇文章会从 SparkContext ...

  4. 模线性方程&&中国剩余定理及拓展

    一.求解模线性方程 由ax=b(mod n) 可知ax = ny + b 就相当于ax + ny = b 由扩展欧几里得算法可知有解条件为gcd(a, n)整除d 可以直接套用扩展欧几里得算法 最终由 ...

  5. MySQL:数据库入门篇4

    1. 视图 创建视图 create view 视图名字 as 查询sql语句; drop view 视图名字; alter view 视图名字 as 查询sql语句; 2. 触发器 1. 插入事件触发 ...

  6. LA 4043 最优匹配

    题目链接:https://vjudge.net/contest/161820#problem/A 题意: n 个 白点,n 个黑点,给出了坐标,求完美匹配后,各点不相交,输出白点对于的黑点编号:(输出 ...

  7. ACM/ICPC 2018亚洲区预选赛北京赛站网络赛 B Tomb Raider 【二进制枚举】

    任意门:http://hihocoder.com/problemset/problem/1829 Tomb Raider 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 L ...

  8. DisparityCostVolumeEstimator.cpp

    #include "DisparityCostVolumeEstimator.hpp" #include "DisparityCostVolume.hpp" # ...

  9. CodeForces 501B Misha and Changing Handles(STL map)

    Misha hacked the Codeforces site. Then he decided to let all the users change their handles. A user ...

  10. [Oracle]Oracle表权限小结

    在数据库中,表是我们接触得最多的数据库对象,接下来对与表有关的系统权限与对象权限做一个小结. (1)与表有关的系统权限 CREATE TABLE 在当前Schema中创建.删除.修改表. SELECT ...