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 ...
随机推荐
- The power of now
惊喜的发现,在这个短暂而又漫长的盛夏里心情开始随天气而变了(*^__^*) ...... <秘密>和<当下的力量>两者都一样,看起来费劲,不过还真的有点道理. <冰与火之 ...
- jquery 给每个li增加事件
<ul id = "list-unstyled"> <li>aaa</li> <li>bbb</li> <li&g ...
- 字符串copy推导演变
#include <stdio.h> #include<string.h> /*基本水平*/ void mycopy1(char *des,char * sou) { unsi ...
- Spring中@Resource与@Autowired
问题 这其实就是@Autoware与@Resource没有正确的使用,这个错误是因为wmPoiOplogService这个变量装配方式是@Resource,按照@Resource的按名字查找的方式,并 ...
- 3D跑马灯效果
睡了13个小时,发烧终于退了,持续2周的感冒看起来终于好了点,这一周一直在看perspective的一些资料,写一个3D跑马灯的效果. 个人感觉主要就是理解视角的概念,也就是perspective和p ...
- C语言 · 三个整数的排序
算法提高 三个整数的排序 时间限制:1.0s 内存限制:256.0MB 问题描述 输入三个数,比较其大小,并从大到小输出. 输入格式 一行三个整数. 输出格式 一行三个整数,从大到小 ...
- 如何在linux下实现mysql数据库每天自动备份
建备份文件夹: mkdir mysql_data_bak 建脚本文件: touch autobackupmysql.sh 打开文件 vi autobackupmysql.sh 在脚本中加入如下内容: ...
- JavaScript 使用 php 的变量
php 里面有一个变量,我想让 js 调用他, 有如下流程: <?php for ($i = 0; $i < 8; $i++) { echo "<tr>"; ...
- HttpGet params not being sent httpget.setParams(params)不好使
错误的代码 HttpClient httpclient = new DefaultHttpClient(); HttpUriRequest request = new HttpGet(uri); Ht ...
- 一站式学习Wireshark(七):Statistics统计工具功能详解与应用
Wireshark一个强大的功能在于它的统计工具.使用Wireshark的时候,我们有各种类型的工具可供选择,从简单的如显示终端节点和会话到复杂的如Flow和IO图表.本文将介绍基本网络统计工具.包括 ...