想当初,我听见大佬们谈起插头DP时,觉得插头DP是个神仙的东西。

某大佬:“考场见到插头DP,直接弃疗。

现在,我终于懂了他们为什么这么说了。

因为——

插头DP很毒瘤!

为什么?

等等你就知道这是为什么了。


例题

hdu 1693 Eat the Trees

题目大意:给你一个地图,每个格子是空地或者障碍。现在要用若干个回路来覆盖这些空地,使得每个空地皆被一个回路覆盖。问方案数。

n,m<=11

How to solve it?

暴力?本人的暴力水平太弱了,对于这题,似乎连暴力都不好打。

So?咋做?

Of course,DP……

怎么划分阶段?

通常有逐行,逐列,逐格。你说的是什么鬼?

字面上的意思,逐行就是从上一行的状态转移到下一行,逐列类似。

逐格就是按照一定的顺序枚举每个格子(比如从上到下,从左到右),从上一个格子转移到下一个格子。

插头DP

插头是什么?

插头DP,最重要的当然是插头。

对于每一个格子,有上下左右四个插头,表示这个格子可以在这个方向与外面相连。

这题中,每个点的插头状况有下面七种。



其中,第0种情况是存在于障碍物的,剩下的6种存在于空地。

那么,我们在转移时,要接上轮廓线上边的插头,使其不会出现断头的情况。

轮廓线?就是你转移时候的一条线(没说一定是直线哈),这条线上面的每个格子都是你之前计算过的。



这幅图就是逐行转移的轮廓线。

做法

考虑逐行转移。

设fi,s" role="presentation">fi,sfi,s表示枚举到第i" role="presentation">ii行,轮廓线上的下插头状态为s" role="presentation">ss的方案数。

怎么转移?

既然这是插头DP,那么转移的时候,就要接上上面的插头。

仔细地考虑一下,转移状态太多,不好枚举,时间复杂度显然无法接受。

那我们就试一下逐格转移。

先放个图。



设fi,j,s" role="presentation">fi,j,sfi,j,s表示,枚举到i" role="presentation">ii行j" role="presentation">jj列,轮廓线上的插头状态为s" role="presentation">ss的方案数。

轮廓线上的插头显然是m" role="presentation">mm个下插头和1个右插头。

那么,转移?

既然要接上之前的插头,那么我们可以让之前的插头来接上它。

比如,1状态的格子的上方要有一个下插头,左边要有一个右插头。

2状态的格子上方要有一个下插头,左边不能有右插头。

那么我们枚举现在的状态,可以算出它从之前的那个状态推过来。

这个需要分类讨论……这就是插头DP毒瘤的原因。

还有一个比较毒瘤的是,最上面一行,最下面一行,以及最左边一列,需要特殊处理。

当然,如果你喜欢,最上面一行也不需特殊处理。

为什么要特殊处理?我觉得我不用说原因了。

代码

下面的代码,注释中例如no*yes,表示上面没有下插头,左边有一个右插头。

话说存状态的话,听说处理连通性的插头DP要按顺序存,这里为图方便,就将右插头存到了最后。

/*Situation:
1:up left
2:down up
3:down left
4:down right
5:right left
6:right up
*/
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
int n,m;
int mat[11][11];
long long f[11][11][4096];
int main()
{
// freopen("in.txt","r",stdin);
// freopen("test.txt","w",stdout);
int T;
scanf("%d",&T);
for (int TT=1;TT<=T;++TT)
{
scanf("%d%d",&n,&m);
for (int i=0;i<n;++i)
for (int j=0;j<m;++j)
scanf("%d",&mat[i][j]);
memset(f,0,sizeof f);
if (mat[0][0])
f[0][0][1<<0|1<<m]=1;//4
else
f[0][0][0]=1;//Space
for (int j=1;j<m;++j)
if (mat[0][j])
{
for (int s_=0;s_<1<<j+1;++s_)
if (s_>>j&1)
{
int s=s_|1<<m;
f[0][j][s]+=f[0][j-1][s_^1<<j];//no*no->4
s=s_;
f[0][j][s]+=f[0][j-1][s_^1<<j|1<<m];//no*yes->3
}
else
{
int s=s_|1<<m;
f[0][j][s]+=f[0][j-1][s_|1<<m];//no*yes->5
}
}
else
{
for (int s=0;s<1<<j;++s)
f[0][j][s]+=f[0][j-1][s];
}
for (int i=1;i<n-1;++i)
{
if (mat[i][0])
{
for (int s_=0;s_<(1<<m);++s_)
if (s_&1)
{
int s=s_|1<<m;
f[i][0][s]+=f[i-1][m-1][s_^1];//no*no->4
s=s_;
f[i][0][s]+=f[i-1][m-1][s_];//yes*no->2
}
else
{
int s=s_|1<<m;
f[i][0][s]+=f[i-1][m-1][s_|1];//yes*no->6
}
}
else
{
for (int s=0;s<(1<<m);s+=2)
f[i][0][s]+=f[i-1][m-1][s];//no*no->Space
}
for (int j=1;j<m;++j)
if (mat[i][j])
{
for (int s_=0;s_<1<<m;++s_)
if (s_>>j&1)
{
int s=s_|1<<m;
f[i][j][s]+=f[i][j-1][s_^1<<j];//no*no->4
s=s_;
f[i][j][s]+=f[i][j-1][s_]+f[i][j-1][s_^1<<j|1<<m];//yes*no->2 no*yes->3
}
else
{
int s=s_|1<<m;
f[i][j][s]+=f[i][j-1][s_|1<<m]+f[i][j-1][s_|1<<j];//no*yes->5 yes*no->6
s=s_;
f[i][j][s]+=f[i][j-1][s_|1<<j|1<<m];//yes*yes->1
}
}
else
{
for (int s=0;s<1<<m;++s)
if (!(s>>j&1))
f[i][j][s]+=f[i][j-1][s];//no*no->Space
}
}
if (mat[n-1][0])
{
for (int s_=0;s_<(1<<m);++s_)
if (!(s_&1))
{
int s=s_|1<<m;
f[n-1][0][s]+=f[n-1-1][m-1][s_|1];//yes*no->6
}
}
else
{
for (int s=0;s<(1<<m);s+=2)
f[n-1][0][s]+=f[n-1-1][m-1][s];//no*no->Space
}
for (int j=1;j<m;++j)
if (mat[n-1][j])
{
for (int s_=0;s_<1<<m;++s_)
if (!(s_>>j&1))
{
int s=s_|1<<m;
f[n-1][j][s]+=f[n-1][j-1][s_|1<<m]+f[n-1][j-1][s_|1<<j];//no*yes->5 yes*no->6
s=s_;
f[n-1][j][s]+=f[n-1][j-1][s_|1<<j|1<<m];//yes*yes->1
}
}
else
{
for (int s=0;s<1<<m;++s)
if (!(s>>j&1))
f[n-1][j][s]+=f[n-1][j-1][s];//no*no->Space
}
long long ans=f[n-1][m-1][0];
printf("Case %d: There are %lld ways to eat the trees.\n",TT,ans);
}
return 0;
}

总结

插头DP的关键是,转移的时候要连上之前的插头,保证不要断掉。

打插头DP需要非常严谨,打错了就不妙了,还不好调。

某大佬说,这不是标准的插头DP,因为标准的插头DP还要维护连通性,好像是用什么括号序。

我才懒得管这么多,感性理解一下,插头DP就是有插头的DP吧……不过那些维护连通性的题,若有机会,我定会光顾一下。

这题作为例题,还是很好的。

hdu1693 Eat the Trees [插头DP经典例题]的更多相关文章

  1. HDU1693 Eat the Trees —— 插头DP

    题目链接:https://vjudge.net/problem/HDU-1693 Eat the Trees Time Limit: 4000/2000 MS (Java/Others)    Mem ...

  2. HDU1693 Eat the Trees 插头dp

    原文链接http://www.cnblogs.com/zhouzhendong/p/8433484.html 题目传送门 - HDU1693 题意概括 多回路经过所有格子的方案数. 做法 最基础的插头 ...

  3. HDU 1693 Eat the Trees(插头DP)

    题目链接 USACO 第6章,第一题是一个插头DP,无奈啊.从头看起,看了好久的陈丹琦的论文,表示木看懂... 大体知道思路之后,还是无法实现代码.. 此题是插头DP最最简单的一个,在一个n*m的棋盘 ...

  4. hdu 1693 Eat the Trees——插头DP

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1693 第一道插头 DP ! 直接用二进制数表示状态即可. #include<cstdio> # ...

  5. HDU 1693 Eat the Trees ——插头DP

    [题目分析] 吃树. 直接插头DP,算是一道真正的入门题目. 0/1表示有没有插头 [代码] #include <cstdio> #include <cstring> #inc ...

  6. hdu1693:eat trees(插头dp)

    题目大意: 题目背景竟然是dota!屠夫打到大后期就没用了,,只能去吃树! 给一个n*m的地图,有些格子是不可到达的,要把所有可到达的格子的树都吃完,并且要走回路,求方案数 题解: 这题大概是最简单的 ...

  7. hdu1693 Eat the Trees 【插头dp】

    题目链接 hdu1693 题解 插头\(dp\) 特点:范围小,网格图,连通性 轮廓线:已决策点和未决策点的分界线 插头:存在于网格之间,表示着网格建的信息,此题中表示两个网格间是否连边 状态表示:当 ...

  8. [Hdu1693]Eat the Trees(插头DP)

    Description 题意:在n*m(1<=N, M<=11 )的矩阵中,有些格子有树,没有树的格子不能到达,找一条或多条回路,吃完所有的树,求有多少种方法. Solution 插头DP ...

  9. 2019.01.23 hdu1693 Eat the Trees(轮廓线dp)

    传送门 题意简述:给一个有障碍的网格图,问用若干个不相交的回路覆盖所有非障碍格子的方案数. 思路:轮廓线dpdpdp的模板题. 同样是讨论插头的情况,只不过没有前一道题复杂,不懂的看代码吧. 代码: ...

随机推荐

  1. 校园商铺-2项目设计和框架搭建-5配置maven

    /src/main/java 存放业务的Java代码 /src/main/resources 存储项目所用到的资源文件,如各种Spring,batis,日志的配置文件 /src/test/java 单 ...

  2. Photoshop基本操作

    PS 工具是我们使用频率比较高的软件之一, 我们学习PS目的不是为了设计海报做电商和UI的,而是要求: 会简单的抠图 会简单的修改PSD效果图 熟练的切图 能和网站美工美眉有共同话题..... Pho ...

  3. DelphiHookApi(经典)

    论坛里有关于HOOK API的贴子, 但其实现在方式显示得麻烦, 其实现在拦截API一般不用那种方式, 大都采用inline Hook API方式.其实也就是直接修改了要拦截的API源码的头部,让它无 ...

  4. nginx css,js合并插件,淘宝nginx合并js,css插件

    先下载Nginx_concat_module,下载后把它放在/usr/local/src/文件夹中,新建文件夹nginx-http-concat把下载的config  ngx_http_concat_ ...

  5. Andriod Fragment 的作用和基本用法

    1.什么是Fragment: Fragment (片段)在Google Android 开发指南中的解释是:片段是Activity中的一部分,一个Activity中可以有多个Fragment.一个Fr ...

  6. PAT甲级——A1110 Complete Binary Tree【25】

    Given a tree, you are supposed to tell if it is a complete binary tree. Input Specification: Each in ...

  7. Font Awesome 完全兼容 Bootstrap 的所有组件。

    "F_FullName": "其他", "F_Icon": "glyphicon glyphicon-backward fa-lg ...

  8. LTE基本架构

    1.LTE结构 这是一张非常有名的LTE架构图,从图中可以看出,整个网络构架被分为了四个部分:  (1)UE就可以看作是我们的手机终端 (2)PDN可以看作是网络上的服务器 (3)E-UTRAN可以看 ...

  9. 17.获取代理ip

    import redis import telnetlib import urllib.request from bs4 import BeautifulSoup r = redis.Redis(ho ...

  10. PagedListCore的使用

    关于在Core2.0中PagedListCore实现分页 一.引言 开发中在对大量数据展示的时候,出于对性能的考虑,我们往往是使用分页功能(用户需要那一页我们就返回给他那一页,而不是一次性加载所有的数 ...