HDU1693 Eat the Trees —— 插头DP
题目链接:https://vjudge.net/problem/HDU-1693
Eat the Trees
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4505 Accepted Submission(s): 2290
So Pudge’s teammates give him a new assignment—Eat the Trees!
The trees are in a rectangle N * M cells in size and each of the cells either has exactly one tree or has nothing at all. And what Pudge needs to do is to eat all trees that are in the cells.
There are several rules Pudge must follow:
I. Pudge must eat the trees by choosing a circuit and he then will eat all trees that are in the chosen circuit.
II. The cell that does not contain a tree is unreachable, e.g. each of the cells that is through the circuit which Pudge chooses must contain a tree and when the circuit is chosen, the trees which are in the cells on the circuit will disappear.
III. Pudge may choose one or more circuits to eat the trees.
Now Pudge has a question, how many ways are there to eat the trees?
At the picture below three samples are given for N = 6 and M = 3(gray square means no trees in the cell, and the bold black line means the chosen circuit(s))
For each case, the first line contains the integer numbers N and M, 1<=N, M<=11. Each of the next N lines contains M numbers (either 0 or 1) separated by a space. Number 0 means a cell which has no trees and number 1 means a cell that has exactly one tree.
6 3
1 1 1
1 0 1
1 1 1
1 1 1
1 0 1
1 1 1
2 4
1 1 1 1
1 1 1 1
Case 2: There are 2 ways to eat the trees.
题意:
用一个或者多个回路去经过所有空格子,问总共有多少种情况?
题解:
有关路径的插头DP。
1.由于只需要记录轮廓线上m+1个位置的插头情况(0或1),且m<=11,2^11 = 2048,故可以用二进制对轮廓线的信息进行压缩。
2.接着就是人工枚举了。
对于如何维护轮廓线的理解:
1.对于棋盘。我们需要将其开始下标设为1(方便维护轮廓线)。
2.将要处理第i行第1列的时候,它的左插头的信息记录在第0位,它的上插头的信息刚好记录在第1位。
3.当处理完第i行第1列的时候,我们就把它的左插头改为下插头,上插头改为右插头。这样,对于第i行第2列来说,它的左插头记录在第1位,上插头记录在第2位。所以可以总结出:对于当前要转移的格子a[i][j],它的左插头的信息记录在第j-1位,它的上插头记录在第j位。
4.当转移完第i行最后一个格子的时候,左插头就位于轮廓线的最后位置,即其信息记录在最高位。而我们在转到下一行第一列的时候,由于a[i+1][1]的左插头是记录在第0位的,所以就需要把最高位的移到第0位,后面的位又往后移动一位,即左移。我们又知道:第一列的格子是没有左插头的,所以,在转行的时候,有效的轮廓线是没有左插头轮廓线,我们取这些有效的轮廓线,并对其压缩信息左移一位即可。
写法一:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <string>
#include <set>
using namespace std;
typedef long long LL;
const int INF = 2e9;
const LL LNF = 9e18;
const int MOD = 1e9+;
const int MAXN = 1e5; int n, m, Map[][];
LL dp[][][<<]; int main()
{
int T, kase = ;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
for(int i = ; i<=n; i++)
for(int j = ; j<=m; j++)
scanf("%d", &Map[i][j]); memset(dp, , sizeof(dp));
dp[][m][] = ; //初始状态
for(int i = ; i<=n; i++)
{
//可知转移完行末的格子后,左插头在轮廓线上最后一个位置,即其信息记录在最后一个位
//由于第一列的格子肯定没有左插头,所以只需枚举到(1<<m)-1,以去除掉含有左插头的情况。
for(int s = ; s<(<<m); s++)
dp[i][][s<<] = dp[i-][m][s]; for(int j = ; j<=m; j++)
{
for(int s = ; s<(<<m+); s++) //枚举上一个格子的所以状态,即当前格子的轮廓线
{
int up = <<j; //上插头
int left = <<(j-); //左插头
bool have_up = s&up;
bool have_left = s&left; if(!Map[i][j]) //当前格子不可行
{
if( !have_up && !have_left ) //当没有插头时,才能转移
dp[i][j][s] += dp[i][j-][s];
}
else //当前格子可行
{
if( !have_up && !have_left ) //两个格子都不存在,新建分量
{
dp[i][j][s^up^left] += dp[i][j-][s];
}
else if( have_up && !have_left ) //有上插头无左插头
{
dp[i][j][s] += dp[i][j-][s]; //往右延伸
dp[i][j][s^up^left] += dp[i][j-][s]; //往下延伸
}
else if( !have_up && have_left ) //无上插头有左插头
{
dp[i][j][s] += dp[i][j-][s]; //往下延伸
dp[i][j][s^up^left] += dp[i][j-][s]; //往右延伸
}
else //两个插头都存在,只能合并分量
{
dp[i][j][s^up^left] += dp[i][j-][s];
}
}
}
}
}
printf("Case %d: There are %lld ways to eat the trees.\n", ++kase, dp[n][m][]);
}
}
写法二(写法一的简写):
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <string>
#include <set>
using namespace std;
typedef long long LL;
const int INF = 2e9;
const LL LNF = 9e18;
const int MOD = 1e9+;
const int MAXN = 1e5; int n, m, Map[][];
LL dp[][][<<]; int main()
{
int T, kase = ;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
for(int i = ; i<=n; i++)
for(int j = ; j<=m; j++)
scanf("%d", &Map[i][j]); memset(dp, , sizeof(dp));
dp[][m][] = ; //初始状态
for(int i = ; i<=n; i++)
{
//可知转移完行末的格子后,左插头在轮廓线上最后一个位置,即其信息记录在最后一个位
//由于第一列的格子肯定没有左插头,所以只需枚举到(1<<m)-1,以去除掉含有左插头的情况。
for(int s = ; s<(<<m); s++)
dp[i][][s<<] = dp[i-][m][s]; for(int j = ; j<=m; j++)
{
for(int s = ; s<(<<m+); s++) //枚举上一个格子的所以状态,即当前格子的轮廓线
{
int up = <<j; //上插头
int left = <<(j-); //左插头
bool have_up = s&up;
bool have_left = s&left; if(!Map[i][j]) //当前格子不可行
{
if( !have_up && !have_left ) //当没有插头时,才能转移
dp[i][j][s] += dp[i][j-][s];
}
else //当前格子可行
{
dp[i][j][s^up^left] += dp[i][j-][s];
if( (have_up&&!have_left) || (!have_up&&have_left) )
dp[i][j][s] += dp[i][j-][s];
}
}
}
}
printf("Case %d: There are %lld ways to eat the trees.\n", ++kase, dp[n][m][]);
}
}
对写法一用哈希表优化了一下,但是过不了,差不出错误。先放着:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <string>
#include <set>
using namespace std;
typedef long long LL;
const int INF = 2e9;
const LL LNF = 9e18;
const int MOD = 1e9+;
const int MAXN = 1e5;
const int HASH = 1e4; int n, m, Map[][]; struct
{
int size, head[HASH], next[MAXN];
int state[MAXN];
LL sum[MAXN]; void init()
{
size = ;
memset(head, -, sizeof(head));
} void insert(int status, LL Sum)
{
int u = status%HASH;
for(int i = head[u]; i!=-; i = next[i])
{
if(state[i]==status)
{
sum[i] += Sum;
return;
}
}
state[size] = status; //头插法
sum[size] = Sum;
next[size] = head[u];
head[u] = size++;
} }Hash_map[]; void transfer(int i, int j, int cur)
{
for(int k = ; k<Hash_map[cur].size; k++)
{
int s = Hash_map[cur].state[k];
LL Sum = Hash_map[cur].sum[k]; int up = <<j;
int left = <<(j-);
bool have_up = s&up;
bool have_left = s&left; if(!Map[i][j])
{
Hash_map[cur^].insert(s<<(j==m), Sum);
}
else
{
if( !have_up && !have_left )
{
if(Map[i+][j] && Map[i][j+])
Hash_map[cur^].insert((s^up^left)<<(j==m), Sum);
}
else if( have_up && !have_left ) //有上插头无左插头
{
if(Map[i][j+])
Hash_map[cur^].insert(s<<(j==m), Sum);
if(Map[i+][j])
Hash_map[cur^].insert((s^up^left)<<(j==m), Sum);
}
else if( !have_up && have_left ) //无上插头有左插头
{
if(Map[i][j+])
Hash_map[cur^].insert((s^up^left)<<(j==m), Sum);
if(Map[i+][j])
Hash_map[cur^].insert(s<<(j==m), Sum);
}
else
{
Hash_map[cur^].insert((s^up^left)<<(j==m), Sum);
}
}
}
} int main()
{
int T, kase = ;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
memset(Map, , sizeof(Map));
for(int i = ; i<=n; i++)
for(int j = ; j<=m; j++)
scanf("%d", &Map[i][j]); int cur = ;
Hash_map[cur].init();
Hash_map[cur].insert(, );
for(int i = ; i<=n; i++)
{
for(int j = ; j<=m; j++)
{
Hash_map[cur^].init();
transfer(i, j, cur);
cur ^= ;
}
}
printf("Case %d: There are %lld ways to eat the trees.\n", ++kase, Hash_map[cur].sum[]);
}
}
HDU1693 Eat the Trees —— 插头DP的更多相关文章
- HDU1693 Eat the Trees 插头dp
原文链接http://www.cnblogs.com/zhouzhendong/p/8433484.html 题目传送门 - HDU1693 题意概括 多回路经过所有格子的方案数. 做法 最基础的插头 ...
- hdu1693 Eat the Trees [插头DP经典例题]
想当初,我听见大佬们谈起插头DP时,觉得插头DP是个神仙的东西. 某大佬:"考场见到插头DP,直接弃疗." 现在,我终于懂了他们为什么这么说了. 因为-- 插头DP很毒瘤! 为什么 ...
- 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 the Trees 【插头dp】
题目链接 hdu1693 题解 插头\(dp\) 特点:范围小,网格图,连通性 轮廓线:已决策点和未决策点的分界线 插头:存在于网格之间,表示着网格建的信息,此题中表示两个网格间是否连边 状态表示:当 ...
- hdu1693:eat trees(插头dp)
题目大意: 题目背景竟然是dota!屠夫打到大后期就没用了,,只能去吃树! 给一个n*m的地图,有些格子是不可到达的,要把所有可到达的格子的树都吃完,并且要走回路,求方案数 题解: 这题大概是最简单的 ...
- [Hdu1693]Eat the Trees(插头DP)
Description 题意:在n*m(1<=N, M<=11 )的矩阵中,有些格子有树,没有树的格子不能到达,找一条或多条回路,吃完所有的树,求有多少种方法. Solution 插头DP ...
- 2019.01.23 hdu1693 Eat the Trees(轮廓线dp)
传送门 题意简述:给一个有障碍的网格图,问用若干个不相交的回路覆盖所有非障碍格子的方案数. 思路:轮廓线dpdpdp的模板题. 同样是讨论插头的情况,只不过没有前一道题复杂,不懂的看代码吧. 代码: ...
随机推荐
- JS控制背景音乐 没有界面
建立一个HTML5页面,放置<audio>标签,设置音频文件源,设置循环播放.准备两张图片,分别表示开启和暂停背景音乐两种状态,可以点击. <audio id="music ...
- HDU 2197 本源串
如果一个串能完全由其子串组成,那么这个串就不是本源串 求长度为n的本源串的个数. 由定义一个串如果不是本源串,那么他的长度一定是组成其子本源串的长度的(>=1) 整数倍. 那么长度为n的串总个数 ...
- 【shell】shell编程(三)-if,select,case语句
通过前两篇文章,我们掌握了shell的一些基本写法和变量的使用,以及基本数据类型的运算.那么,本次就将要学习shell的结构化命令了,也就是我们其它编程语言中的条件选择语句及循环语句. 不过,在学习s ...
- R语言入门---杂记(一)---R的常用函数
1.nchar():查看字符串长度. 2.rev(): 给你的数据翻个个 3.sort():给你数据排个序(默认从小到大依次排列) 4.runif():产生均匀分布的随机数 #runif
- asp.net core系列 65 正反案例介绍SOLID原则
一.概述 SOLID五大原则使我们能够管理解决大多数软件设计问题.由Robert C. Martin在20世纪90年代编写了这些原则.这些原则为我们提供了从紧耦合的代码和少量封装转变为适当松耦合和封装 ...
- mysql主从读写分离,分库分表
1.分表 当项目上线后,数据将会几何级的增长,当数据很多的时候,读取性能将会下降,更新表数据的时候也需要更新索引,所以我们需要分表,当数据量再大的时候就需要分库了. a.水平拆分:数据分成多个表 b. ...
- CSS 居中 可随着浏览器变大变小而居中
关键代码: 外部DIV使用: text-align:center; 内部DIV使用: margin-left:auto;margin-right:auto 例: <div style=" ...
- VC/MFC中计算程序运行时间
转自原文VC/MFC中计算程序运行时间 说明,这四种方法也分别代表了类似的实现,在MFC中,所可以从哪些类集合去考虑. 方法一 利用GetTickCount函数(ms) CString str; lo ...
- 打印报表以显示具有给定责任的用户-FNDSCRUR责任用户
select --&p_hint distinct user_name, decode ( greatest (u.sta ...
- SolidWorks如何绘制抽壳零件
1 绘制一个零件,点击抽壳 2 你可以一个一个面选,也可以直接选中一个零件,对他的所有面都薄壳处理(右击弹出菜单选择确定即可) 3 可以用剖视图检查是否抽壳成功 4 对于复杂的零件,一个一 ...