Description

The cows have reconstructed Farmer John's farm, with its N barns (1 <= N <= 150, number 1..N) after the terrible earthquake last May. The cows didn't have time to rebuild any extra roads, so now there is exactly one way to get from any given barn to any other barn. Thus, the farm transportation system can be represented as a tree.

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

* Line 1: Two integers, N and P

* 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

A single line containing the integer that is the minimum number of roads that need to be destroyed for a subtree of P nodes to be isolated. 

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

[A subtree with nodes (1, 2, 3, 6, 7, 8) will become isolated if roads 1-4 and 1-5 are destroyed.] 

题意:

给定一棵树, 求解最少切除几条边可以得到一个大小为 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. 我的思路

  1. dp[u][j] 表示以 u 为根的子树保留 j 个节点最少切除的边数
  2. 树形 DP 求解父节点时, 一般是先求解子节点, 得到子节点对应的信息, 然后回溯到父节点, 代码的框架基本这样
    dfs(int u) {
    初始化
    for(u 的孩子节点v...) {
    dfs(v)
    }
    }
  3. 某时刻, 恰好要进行 dfs(i), 此刻 dp[u][j] 记录的数据是假设父节点 u 仅有前 i-1 个孩子时的最优解. dp[u][j] 记录的是 u 的前 i-1 个孩子保留 j 个节点的最少切边数
  4. 对 i 执行 dfs(i), 得到以 i 为根的子树保留 j 个节点的最少切边数dp[i][j]
  5. 这时, 我们假设直接切除第 u-i 这条边, 多切除了一条边, 所有的 dp[u][j]+1, 并记录 ans = dp[u][j]+1
  6. (5) 是做了一个假设, 但以 i 为根树的加入可能会使某个 dp[u][j'] 变小, 所以需要判断 dp[u][j-k]+dp[i][k] 与 ans 的关系
  7. 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)的更多相关文章

  1. POJ 1947 Rebuilding Roads 树形DP

    Rebuilding Roads   Description The cows have reconstructed Farmer John's farm, with its N barns (1 & ...

  2. POJ 1947 Rebuilding Roads 树形dp 难度:2

    Rebuilding Roads Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 9105   Accepted: 4122 ...

  3. DP Intro - poj 1947 Rebuilding Roads(树形DP)

    版权声明:本文为博主原创文章,未经博主允许不得转载. Rebuilding Roads Time Limit: 1000MS   Memory Limit: 30000K Total Submissi ...

  4. [poj 1947] Rebuilding Roads 树形DP

    Rebuilding Roads Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 10653 Accepted: 4884 Des ...

  5. POJ 1947 Rebuilding Roads (树dp + 背包思想)

    题目链接:http://poj.org/problem?id=1947 一共有n个节点,要求减去最少的边,行号剩下p个节点.问你去掉的最少边数. dp[u][j]表示u为子树根,且得到j个节点最少减去 ...

  6. poj 2324 Anniversary party(树形DP)

    /*poj 2324 Anniversary party(树形DP) ---用dp[i][1]表示以i为根的子树节点i要去的最大欢乐值,用dp[i][0]表示以i为根节点的子树i不去时的最大欢乐值, ...

  7. 树形dp(poj 1947 Rebuilding Roads )

    题意: 有n个点组成一棵树,问至少要删除多少条边才能获得一棵有p个结点的子树? 思路: 设dp[i][k]为以i为根,生成节点数为k的子树,所需剪掉的边数. dp[i][1] = total(i.so ...

  8. POJ 1947 Rebuilding Roads

    树形DP..... Rebuilding Roads Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 8188 Accepted: ...

  9. POJ 3162.Walking Race 树形dp 树的直径

    Walking Race Time Limit: 10000MS   Memory Limit: 131072K Total Submissions: 4123   Accepted: 1029 Ca ...

随机推荐

  1. python+xpath+requests爬取维基百科历史上的今天

    import requests import urllib.parse import datetime from lxml import etree fhout = open("result ...

  2. 用C# Winform做一个文件名批量修改器

    我是一名QA,我提bug以后有个习惯,就是将bug的jira地址保存为一个链接存在本地,如下: 每天都要手动的把日期“[XX.XX]”添加在里面,这个反复修改文件名的过程是比较枯燥的,于是我决定写一个 ...

  3. spring通过配置xml文件集成quartz定时器

    概述 Spring为创建Quartzde Scheduler.Trigger和JobDetail提供了方便的FactoryBean类,以便能够在Spring容器中享受注入的好处. 此外,Spring还 ...

  4. Linux 系统串口信息查看

    先确认系统启动的时候串口的信息. ECM_5412@chenfl:~$ dmesg | grep tty [ 0.000000] console [tty0] enabled [ 2.511678] ...

  5. uboot中fb实现

    帧缓冲区fb在内存中,要实现fb同步显示需要设定DMA操作. 设定LCD的DMA操作,要在开始操作LCD之前. common/lcd.c中定义lcd_init() --> driver/vide ...

  6. 转 Linux调优方案,sysctl.conf的设置

    $ /proc/sys/net/core/wmem_max最大socket写buffer,可参考的优化值:873200 $ /proc/sys/net/core/rmem_max最大socket读bu ...

  7. 责任链模式 - tomcat

    class filterChain{ private List<Filter> filters; public void addFilter(Filter filter){ filters ...

  8. java-基于JavaMail的Java邮件发送

    1.基于JavaMail的Java邮件发送:简单邮件发送 2.基于JavaMail的Java邮件发送:复杂邮件发送

  9. 《FPGA全程进阶---实战演练》第三章之PCB设计之去耦电容

    1.关于去耦电容为何需要就近摆放? 大多数资料有提到过,去耦电容就近放置,是从减小回路电感的角度去谈及摆放问题,其实还有一个原则就是去耦半径的问题,如果电容离着芯片位置较远,超过去耦半径,会起不到去耦 ...

  10. am335x 10.1"电容touch 不能识别

    /**************************************************************** * am335x 10.1"电容touch 不能识别 * ...