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 ...
随机推荐
- js中作用域和闭包
作用域链实例 (1) function example() { var age = 23; alert(age) } var age = 25; example(); alert(age); // ...
- 修改mysql root账户登录密码
打开mysql.exe(如果你配置了path的话,打开cmd即可)键入: mysqladmin -u root -p password [你的新密码][回车] 然后会要求你输入你当前的密码,就修改成功 ...
- (Python mysql驱动的解决)_mysql.c(42) : fatal error C1083: Cannot open include file: 'config-win.h':问题的解决
在win7下安装了Python后,想安装python-MySQL,使用pip安装出现如下问题: >pip install MySQL-python _mysql.c(42) : fatal er ...
- Android开发日记(三)
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentV ...
- javascript 中解析json
首先温习一下JSON格式: (1) 单个对象{"变量名1":"变量值1","变量名2":"变量值2"} (2) 数组[{ ...
- ios UIImageView异步加载网络图片2
//1. NSData dataWithContentsOfURL // [self.icon setImage:[UIImage imageWithData:[NSData dataWithCont ...
- 【C#】List列表的深复制,引用类型深复制
需求:深复制该列表. Student实体类: public class Student { public string Name { get; set; } public int Age { get; ...
- ubuntu下配置java环境
配置JDK环境 下载 登录oracle的网站去下载JDK7 http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads ...
- 异常:Error:Execution failed for task ':app:compileDebugJavaWithJavac'. > Compilation failed; see the compiler error output for details.
碰到这个异常我也是挺无语的,因为Android Studio根本不会提示你详细的错误信息. 我们来看看这个博主:http://blog.csdn.net/runner__1/article/detai ...
- l2正则化
在机器学习中,无论是分类还是回归,都可能存在由于特征过多而导致的过拟合问题.当然解决的办法有 (1)减少特征,留取最重要的特征. (2)惩罚不重要的特征的权重. 但是通常情况下,我们不知道应该惩罚哪些 ...