想当初,我听见大佬们谈起插头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. [JZOJ6271] 2019.8.4【NOIP提高组A】锻造

    题目 题目大意 武器的每个级别有固定的两种属性\(b_i\)和\(c_i\) 可以用\(a\)的代价得到一把\(0\)级的武器. 可以将\(x\)级武器和\(y=\max(x-1,0)\)级武器融合锻 ...

  2. js 实现 map 工具类

    /* * MAP对象,实现MAP功能 * * 接口: * size() 获取MAP元素个数 * isEmpty() 判断MAP是否为空 * clear() 删除MAP所有元素 * put(key, v ...

  3. (转)java源程序加密解决方案(基于Classloader解密)

    转:http://cjnetwork.iteye.com/blog/851544 源程序加密解决方案 1. 概述: Java源程序的加密,有如下两种: 1使用混淆器对源码进行混淆,降低反编译工具的作用 ...

  4. Systm.IO.File.cs

    ylbtech-Systm.IO.File.cs 1.程序集 mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c5619 ...

  5. python模块(转自Yuan先生)

    模块&包(****)                                                                模块(modue)的概念: 在计算机程序的开 ...

  6. HDU-2095-find your present (2)-位或/STL(set)

    In the new year party, everybody will get a "special present".Now it's your turn to get yo ...

  7. Invalid bound statement (not found)之idea打包maven项目问题

    开发的一个maven项目,之前在Eclipse中,maven打包部署完后一切正常,后来转到idea中开发,再用maven打包部署后, 一直报 Invalid bound statement (not ...

  8. <Python基础>字典的基本操作

    ''' 小知识 1.字典的键只能是不可变数据类型:int 元组 bool str(可哈希) 字典查找数据会使用二分查找,会先用哈希表将键转化为数字然后进行查找 ''' s = { "name ...

  9. Spring 泛型依赖注入(3)

    BaseService<T>:有RoleService和UserService两的子类 BaseRepepositry<T>:有UserRepository和RoleRepos ...

  10. 8.关于ActiveMQ、RocketMQ、RabbitMQ、Kafka一些总结和区别

    这是一篇分享文 转自:http://www.cnblogs.com/williamjie/p/9481780.html  尊重原作,谢谢 消息队列 为什么写这篇文章? 博主有两位朋友分别是小A和小B: ...