hdu1693 Eat the Trees [插头DP经典例题]
想当初,我听见大佬们谈起插头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经典例题]的更多相关文章
- HDU1693 Eat the Trees —— 插头DP
题目链接:https://vjudge.net/problem/HDU-1693 Eat the Trees Time Limit: 4000/2000 MS (Java/Others) Mem ...
- HDU1693 Eat the Trees 插头dp
原文链接http://www.cnblogs.com/zhouzhendong/p/8433484.html 题目传送门 - HDU1693 题意概括 多回路经过所有格子的方案数. 做法 最基础的插头 ...
- HDU 1693 Eat the Trees(插头DP)
题目链接 USACO 第6章,第一题是一个插头DP,无奈啊.从头看起,看了好久的陈丹琦的论文,表示木看懂... 大体知道思路之后,还是无法实现代码.. 此题是插头DP最最简单的一个,在一个n*m的棋盘 ...
- hdu 1693 Eat the Trees——插头DP
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1693 第一道插头 DP ! 直接用二进制数表示状态即可. #include<cstdio> # ...
- HDU 1693 Eat the Trees ——插头DP
[题目分析] 吃树. 直接插头DP,算是一道真正的入门题目. 0/1表示有没有插头 [代码] #include <cstdio> #include <cstring> #inc ...
- hdu1693:eat trees(插头dp)
题目大意: 题目背景竟然是dota!屠夫打到大后期就没用了,,只能去吃树! 给一个n*m的地图,有些格子是不可到达的,要把所有可到达的格子的树都吃完,并且要走回路,求方案数 题解: 这题大概是最简单的 ...
- hdu1693 Eat the Trees 【插头dp】
题目链接 hdu1693 题解 插头\(dp\) 特点:范围小,网格图,连通性 轮廓线:已决策点和未决策点的分界线 插头:存在于网格之间,表示着网格建的信息,此题中表示两个网格间是否连边 状态表示:当 ...
- [Hdu1693]Eat the Trees(插头DP)
Description 题意:在n*m(1<=N, M<=11 )的矩阵中,有些格子有树,没有树的格子不能到达,找一条或多条回路,吃完所有的树,求有多少种方法. Solution 插头DP ...
- 2019.01.23 hdu1693 Eat the Trees(轮廓线dp)
传送门 题意简述:给一个有障碍的网格图,问用若干个不相交的回路覆盖所有非障碍格子的方案数. 思路:轮廓线dpdpdp的模板题. 同样是讨论插头的情况,只不过没有前一道题复杂,不懂的看代码吧. 代码: ...
随机推荐
- Nlog 日志框架简单教程
安装 Nuget获取 配置寻找 会自动寻找在应用程序目录下的NLog.config(大小写敏感) 如何配置config <?xml version="1.0" encodin ...
- RabbitMQ 连接不上
问题 [org.springframework.amqp.AmqpIOException: java.io.IOException] 解决 username: guest password: gues ...
- day34 反射、面向对象内置方法:如__str__、面向对象的软件开发
Python之路,Day21 = 反射.面向对象内置方法:如__str__.面向对象的软件开发 几个内置查看的方法使用 .__base__ 查看类的继承结构.mro() 对象找属性的顺序存在里面 -- ...
- for in循环介绍以及陷阱
大家都知道在JavaScript中提供了两种方式迭代对象: (1)for 循环: (2)for..in循环: 使用for循环进行迭代数组对象,想必大家都已经司空见惯了.但是,使用for.. in循环时 ...
- echarts renderItem-在区间段内展示连续数据
一.需求背景: 贴图说明:要求数据在不同类型的区间段内展示. 二.实现思路及代码 实现方法: 利用echarts的自定义配置:option.series[i].type='custom'中的rende ...
- System.Web.Mvc.ActionResult.cs
ylbtech-System.Web.Mvc.ActionResult.cs 1.程序集 System.Web.Mvc, Version=5.2.3.0, Culture=neutral, Publi ...
- 关于vlfeat做vlad编码问题
这里是官方文档,可以自己查看 在这里,只是想记录一下,我这几天学习vlfeat 做vlad编码的过程,便于以后整理 网上涉及到vlfeat做vlad编码资料较少,而官网上例子又相对简单,主要是那几个参 ...
- assert(断言)
Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常. 语法格式: assert expression 等价于: if not expression: ra ...
- wpf 几种常用控件样式
转自:http://blog.csdn.net/xuejiren/article/details/39449515
- sql 分组统计查询并横纵坐标转换
关于sql 分组统计查询,我们在做报表的时候经常需要用到;今天就在这里整理下; 先附上一段sql代码: if object_id(N'#mytb',N'U') is not null drop tab ...