今天,我,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. 安全隐患,你对X-XSS-Protection头部字段理解可能有误

    0×00. 引言 我曾做过一个调查,看看网友们对关于X-XSS-Protection 字段的设置中,哪一个设置是最差的,调查结果令我非常吃惊,故有此文. 网友们认为 最差的配置是X-XSS-Prote ...

  2. Linux中基于apache httpd的svn服务器搭建与配置

    mod_dav_svn是apache连接svn的模块 yum install subversion mod_dav_svn httpd 配置文件简单说明, SVNParentPath 说明可以在指定的 ...

  3. 如何在SAP CRM里创建和消费Web service

    Created by Wang, Jerry, last modified on Dec 19, 2014 The following steps demonstrates how to expose ...

  4. Kubernetes API server工作原理

    作为Kubernetes的使用者,每天用得最多的命令就是kubectl XXX了. kubectl其实就是一个控制台,主要提供的功能: 1. 提供Kubernetes集群管理的REST API接口,包 ...

  5. BZOJ4538:[HNOI2016]网络(树链剖分,堆)

    Description 一个简单的网络系统可以被描述成一棵无根树.每个节点为一个服务器.连接服务器与服务器的数据线则看做 一条树边.两个服务器进行数据的交互时,数据会经过连接这两个服务器的路径上的所有 ...

  6. javascript运算符——条件、逗号、赋值、()和void运算符 (转载)

    原文出自 作者:小火柴的蓝色理想   javascript中运算符总共有46个,除了前面已经介绍过的算术运算符.关系运算符.位运算符.逻辑运算符之外,还有很多运算符.本文将介绍条件运算符.逗号运算符. ...

  7. Java 的 FileFilter文件过滤,readline读行操作

    package com.cjonline.foundation.evisa; import java.io.BufferedReader; import java.io.File; import ja ...

  8. 史上最简单的SpringCloud教程 | 第四篇:断路器(Hystrix)(Finchley版本)

    转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springcloud/2018/08/30/sc-f4-hystrix/ 本文出自方志朋的博客 在微服务架构中, ...

  9. Python基础—01-认识python,编写第一个程序

    认识python 发展历史:点此查看简介 就业方向: WEB.爬虫.运维.数据分析.机器学习.人工智能.... 版本选择 python2.7是最后一个py2的版本,2020年将不再提供支持 pytho ...

  10. 序列化表单为json对象,datagrid带额外参提交一次查询 后台用Spring data JPA 实现带条件的分页查询 多表关联查询

    查询窗口中可以设置很多查询条件 表单中输入的内容转为datagrid的load方法所需的查询条件向原请求地址再次提出新的查询,将结果显示在datagrid中 转换方法看代码注释 <td cols ...