POJ 1947 Rebuilding Road(树形DP)
Description
Farmer John wants to know how much damage another earthquake could do. He wants to know the minimum number of roads whose destruction would isolate a subtree of exactly P (1 <= P <= N) barns from the rest of the barns.
Input
* Lines 2..N: N-1 lines, each with two integers I and J. Node I is node J's parent in the tree of roads.
Output
Sample Input
11 6
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11
Sample Output
2
Hint
题意:
给定一棵树, 求解最少切除几条边可以得到一个大小为 P 的子树
思路:
1. 这题是我做过的最难理解的 DP 题目了(假如我的脑子没退化), 我顺着别人的代码单步调试才搞清楚 DP 的思路, VS 的单步调试真是神器, 不仅可以 debug, 更能帮助理解代码
2. 先贴上网上广为流传的思路, dp[u][j] 表示以 u 为根的子树保留 j 个节点的最少切除边数. 对于 u 的某一个孩子 v, 假如保留 v 的话, 那么
dp[u][j] = min(1+dp[u][j], dp[u][j-k]+dp[v][k]). 假如不保留 v 的话, 那么 dp[u][j] = dp[u][j]+1
3. 我的思路
- dp[u][j] 表示以 u 为根的子树保留 j 个节点最少切除的边数
- 树形 DP 求解父节点时, 一般是先求解子节点, 得到子节点对应的信息, 然后回溯到父节点, 代码的框架基本这样
dfs(int u) {
初始化
for(u 的孩子节点v...) {
dfs(v)
}
} - 某时刻, 恰好要进行 dfs(i), 此刻 dp[u][j] 记录的数据是假设父节点 u 仅有前 i-1 个孩子时的最优解. dp[u][j] 记录的是 u 的前 i-1 个孩子保留 j 个节点的最少切边数
- 对 i 执行 dfs(i), 得到以 i 为根的子树保留 j 个节点的最少切边数dp[i][j]
- 这时, 我们假设直接切除第 u-i 这条边, 多切除了一条边, 所有的 dp[u][j]+1, 并记录 ans = dp[u][j]+1
- (5) 是做了一个假设, 但以 i 为根树的加入可能会使某个 dp[u][j'] 变小, 所以需要判断 dp[u][j-k]+dp[i][k] 与 ans 的关系
- dp[u][j] = min(ans, dp[u][j-k]+dp[i][k])
总结:
1. 这道题我看着别人的代码, 用 VS 的单步调试才弄明白, 变量的初始化非常神奇, 甚至有些不合逻辑(dp[u][1] = 0 就与 dp 的定义不符), 但使用起来, 却是极好的
2. 以前做树形 dp 题时, 总怕重复计算, 但从这道题中才完全明白, 树形 dp 常用的状态转移方程 dp[u][j] = min( dp[u][j], dp[u][j-k]+dp[v][k]) 表示
dp[u][j] (前 i 个孩子) = min( dp[u][j](前 i-1 个孩子), dp[u][j-k](前 i-1 个孩子)+dp[i][k](第 i 个孩子))
3. 就像(2) 所描述的那样, 对 u 的第 i-1 个孩子进行计算的时候, u 并不知道其是否有第 i 个孩子, 所以, dp[u][1] 初始化为 0 也可以理解成符合逻辑 --- 刚开始假设 u 没有孩子节点, 那么 dp[u][1] 就是 0
3. 这道题中的第二层循环, v = V...0 仍是为了防止重复计算, 从状态转移方程也可以看出, dp[u][j] (前 i 个孩子) = min( dp[u][j](前 i-1 个孩子), dp[u][j-k](前 i-1 个孩子)+dp[i][k](第 i 个孩子)). 假如 v = 0...V, 那么状态转移方程就变成了 dp[u][j] (前 i 个孩子) = min( 1+dp[u][j](前 i-1 个孩子), dp[u][j-k](前 i 个孩子)+dp[i][k](第 i 个孩子))
4. 第三层循环 k 的遍历顺序就没什么要求了, 因为 k 的遍历是状态转移方程的非递归写法 dp[u][j] = min( 1+dp[u][j], dp[u][j-1]+dp[i][1], dp[u][j-2]+dp[i][2], ... dp[u][0]+dp[i][k]). 从状态转移方程中也能看出, 遍历顺序无关紧要, 没有依赖的问题
5. 初始化非常 tricky, 我自己是断然想不出的
6. 建树方法有点意思, 不理解要什么那样建树, 可能是兼顾效率与能力吧, 但不理解为什么不能随意取点作为大树的根
代码
#include <iostream>
#include <algorithm>
using namespace std; const int MAXN = ;
const int INFS = 0x3fffffff;
int dp[MAXN][MAXN], U[MAXN], V[MAXN];
bool vis[MAXN]; void treedp(int u, int vol, int n)
{
for (int v = ; v <= vol; ++v)
dp[u][v] = INFS;
dp[u][] = ; for (int i = ; i < n; ++i)
{
if (u != U[i])
continue ; treedp(V[i], vol, n);
for (int v = vol; v >= ; --v)
{
int ans = INFS;
if (dp[u][v] != INFS)
ans = dp[u][v] + ; for (int p = ; p <= v; ++p)
if (dp[u][p] != INFS && dp[V[i]][v - p] != INFS)
ans = min(ans, dp[u][p] + dp[V[i]][v - p]); dp[u][v] = ans;
}
}
} int main()
{
int n, p;
while (scanf("%d %d", &n, &p) != EOF)
{
memset(vis, false, sizeof(vis));
for (int i = ; i < n; ++i)
{
scanf("%d %d", &U[i], &V[i]);
vis[V[i]] = true;
} int rt;
for (int i = ; i <= n; ++i)
if (!vis[i])
rt = i; treedp(rt, p, n); int ans = dp[rt][p];
for (int i = ; i <= n; ++i)
if (dp[i][p] < ans)
ans = dp[i][p] + ; printf("%d\n", ans);
}
return ;
}
POJ 1947 Rebuilding Road(树形DP)的更多相关文章
- POJ 1947 Rebuilding Roads 树形DP
Rebuilding Roads Description The cows have reconstructed Farmer John's farm, with its N barns (1 & ...
- POJ 1947 Rebuilding Roads 树形dp 难度:2
Rebuilding Roads Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 9105 Accepted: 4122 ...
- DP Intro - poj 1947 Rebuilding Roads(树形DP)
版权声明:本文为博主原创文章,未经博主允许不得转载. Rebuilding Roads Time Limit: 1000MS Memory Limit: 30000K Total Submissi ...
- [poj 1947] Rebuilding Roads 树形DP
Rebuilding Roads Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 10653 Accepted: 4884 Des ...
- POJ 1947 Rebuilding Roads (树dp + 背包思想)
题目链接:http://poj.org/problem?id=1947 一共有n个节点,要求减去最少的边,行号剩下p个节点.问你去掉的最少边数. dp[u][j]表示u为子树根,且得到j个节点最少减去 ...
- poj 2324 Anniversary party(树形DP)
/*poj 2324 Anniversary party(树形DP) ---用dp[i][1]表示以i为根的子树节点i要去的最大欢乐值,用dp[i][0]表示以i为根节点的子树i不去时的最大欢乐值, ...
- 树形dp(poj 1947 Rebuilding Roads )
题意: 有n个点组成一棵树,问至少要删除多少条边才能获得一棵有p个结点的子树? 思路: 设dp[i][k]为以i为根,生成节点数为k的子树,所需剪掉的边数. dp[i][1] = total(i.so ...
- POJ 1947 Rebuilding Roads
树形DP..... Rebuilding Roads Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 8188 Accepted: ...
- POJ 3162.Walking Race 树形dp 树的直径
Walking Race Time Limit: 10000MS Memory Limit: 131072K Total Submissions: 4123 Accepted: 1029 Ca ...
随机推荐
- 壮哉大微软,.Net人的春天来了,你准备好了嘛!
http://www.cnblogs.com/hguchen/p/5621809.html *********************** 一.序言 2016年6月27日的早晨,微软在 Red Hat ...
- Jbpm4.4 使用
最近工作项目中需要用到工作流.于是找到了jbpm.关于jbpm的一些概念就不说了 1) 首先下载jbpm,这里我选择了jbpm4.4 从官网上可以下载 http://sourceforge.net/ ...
- 一款基于TweenMax.js的网页幻灯片
之前介绍了好多网页幻灯片.今天给大家带来一款基于TweenMax.js的网页幻灯片.这款幻灯片以不规则的碎片百叶窗的形式切换.切换效果非常漂亮.一起看下效果图: 在线预览 源码下载 实现的代码. ...
- 在centos中安装mangodb
1.下载完安装包,并解压 tgz(以下演示的是 64 位 Linux上的安装) curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_6 ...
- 查看局域网其它电脑的计算机名和IP
一.下面脚本可查看局域网中的电脑计算机名和IP,保存下面文本至记事本.后缀改成bat COLOR 0A CLS @ECHO Off Title 查询局域网内在线电脑IP :send @ECHO off ...
- 【WPF】退出应用时的提示弹窗
需求:按MainWindow右上角的[X]想要退出软件时,弹窗提示是否确定要退出. 主界面(ShellWindow)的前台给主界面窗体添加两个事件:Closing和Closed <Window ...
- 指针的引用(*&)与指针的指针(**)
指针的引用(*&)与指针的指针(**) 在下列函数声明中,为什么要同时使用*和&符号?以及什么场合使用这种声明方式? void func1( MYCLASS *&pBuildi ...
- RESTful测试工具-RESTClient
很多测试人可能对RESTful的概念还是很模糊的,那么到底什么是RESTful?百度百科给出的一句话描述是一个架构样式的网络系统,似乎还是有点不懂?OK,说到Restful,我们一般从REST开始说起 ...
- 对设计领域中Tile和Card的理解
前端工程师离不开设计, 谈到设计就要想到大名鼎鼎的material design主题, 而material是以card为经典单元的, card即卡片, 是层次化模型的最小模块, 用于提供扁平化的信息, ...
- js学习笔记18----元素创建操作
1.父级.appendChild(新的元素) 从后面开始追加子元素. 2.父级.insertBefore(新的元素,被插入的元素) 在指定元素前面开始插入一个新元素. 兼容性:在ie下,如果第二个参数 ...