HDU2196Computer(树形DP)

Computer

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 6492    Accepted Submission(s): 3273


Problem Description
A school bought the first computer some time ago(so this computer's id is 1). During the recent years the school bought N-1 new computers. Each new computer was connected to one of settled earlier. Managers of school are anxious about slow functioning of the net and want to know the maximum distance Si for which i-th computer needs to send signal (i.e. length of cable to the most distant computer). You need to provide this information. 


Hint: the example input is corresponding to this graph. And from the graph, you can see that the computer 4 is farthest one from 1, so S1 = 3. Computer 4 and 5 are the farthest ones from 2, so S2 = 2. Computer 5 is the farthest one from 3, so S3 = 3. we also get S4 = 4, S5 = 4.
 

Input
Input file contains multiple test cases.In each case there is natural number N (N<=10000) in the first line, followed by (N-1) lines with descriptions of computers. i-th line contains two natural numbers - number of computer, to which i-th computer is connected and length of cable used for connection. Total length of cable does not exceed 10^9. Numbers in lines of input are separated by a space.
 

Output
For each case output N lines. i-th line must contain number Si for i-th computer (1<=i<=N).
 

Sample Input
  
  
5 1 1 2 1 3 1 1 1
 

Sample Output
  
  
3 2 3 4 4

来自kuangbin的题解: 传送门
经典的树形DP题。
题意是求树中每个点到所有叶子节点的距离的最大值是多少。
由于对于一个节点来说,可能得到的距离最大的值的路径来自他的子树,或者从他的父节点过来,所以用两次DFS。
第一次DFS求出所有节点在他的子树范围内到叶子节点距离的最大值和第二大的值,第二次DFS更新从父节点过来的情况就可以了。
因为如果只存最大值的话,判断一个点的从父节点过来的最大值,那么如果他的父节点存的最大值正好是从该点过来的,那么就失去了从父节点过来的状态,所以要记录最大的两个值。

如果对于求第二大值还是不理解的话:可以看一下这篇博客,但是我认为他其中有一点点交代不清,故做了一些小更改,如下。传送门


思路:

把无根树转化成有根树分析,

对于上面那棵树,要求距结点2的最长距离,那么,就需要知道以2为顶点的子树(蓝色圈起的部分,我们叫它Tree(2)),距顶点2的最远距离L1

还有知道2的父节点1为根节点的树Tree(1)部分(即红色圈起部分),距离结点1的最长距离+dist(1,2) = L2,那么最终距离结点2最远的距离就是max{L1,L2}


f[i][0],表示顶点为i的子树的,距顶点i的最长距离
f[i][1],表示Tree(i的父节点)的最长距离+i跟i的父节点距离


要求所有的f[i][0]很简单,只要先做一次dfs求每个结点到叶子结点的最长距离即可。
然后要求f[i][1], 可以从父节点递推到子节点,

假设节点u有n个子节点,分别是v1,v2...vn
那么
如果vi不是u最长距离经过的节点,f[vi][1] = dist(vi,u)+max(f[u][0], f[u][1])
如果vi是u最长距离经过的节点,那么不能选择f[u][0],因为这保存的就是最长距离,要选择Tree(u)第二大距离secondDist,
可得f[vi][1] = dist(vi, u) + max(secondDist, f[u][1])



收获:

1.代码实现中,dfs1与dfs2在搜索的方式上是有不同的。dfs1找的是顶点u的最长距离,每层的dfs1需要下一层的dfs1处理完毕返回后才能更新最长距离。而dfs2是一层处理完接着一层,从树的根节点开始,逐层向下,不需要下一层的返回就能完成更新。

2.dfs2中v才是当前需要更新最长距离的点,u是v的父节点,p是u的父节点。禁止v==p,也就是禁止找u的父节点,是为了防止dfs2返回到上层,导致递归没有了出口。在更新v的时候,u的最长距离就是最终的答案,所以使用u的次小距离实际上是不会有错的,这里需要仔细想想。

3.每次更新值都是先与次小距离比较,再是次小距离与最长距离比较,然后更新。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN = 10010;
struct Node
{
    int to, next, w;
}edge[MAXN*2];//因为存无向边,所以需要2倍
int tol;
int head[MAXN];//头结点
int maxn[MAXN];//该节点往下到叶子的最大距离
int smaxn[MAXN];//次大距离
int maxid[MAXN];//最大距离对应的相邻节点序号
int smaxid[MAXN];//次大的序号
void init()
{
    tol = 0;
    memset(head, -1, sizeof(head));
}
void add(int u, int v, int w)
{
    edge[tol].to = v;
    edge[tol].w = w;
    edge[tol].next = head[u];
    head[u] = tol++;
    edge[tol].to = u;
    edge[tol].w = w;
    edge[tol].next = head[v];
    head[v] = tol++;
}
//求结点u往下到叶子结点的最大距离
//p是u的父亲结点
void dfs1(int u, int p)
{
    maxn[u] = 0;//每次都必须赋值0,因为是多组数据,否则会导致错误
    smaxn[u] = 0;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if(v == p)//不能往上找父亲结点
            continue;
        dfs1(v, u);
        if(smaxn[u] < edge[i].w + maxn[v])
        {
            smaxn[u] = edge[i].w + maxn[v];
            smaxid[u] = v;
            if(smaxn[u] > maxn[u])
            {
                swap(smaxn[u], maxn[u]);
                swap(smaxid[u], maxid[u]);
            }
        }
    }
}
//u为v的父节点,求出父节点u的最长距离+uv之间的距离来更新v的最长距离
void dfs2(int u, int p)
{
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;//v才是当前待更新最长距离的节点,不是u
        if(v == p)//如果没有禁止往上找父节点,下面的dfs2会导致父子节点之间不断相互调用dfs2
            continue;
        if(v == maxid[u])//如果u的最长距离经过v
        {
            if(smaxn[u] + edge[i].w > smaxn[v])
            {
                smaxn[v] = smaxn[u] + edge[i].w;
                smaxid[v] = u;
                if(smaxn[v] > maxn[v])
                {
                    swap(smaxn[v], maxn[v]);
                    swap(smaxid[v], maxid[v]);
                }
            }
        }
        else
        {
            if(maxn[u] + edge[i].w > smaxn[v])
            {
                smaxn[v] = maxn[u] + edge[i].w;
                smaxid[v] = u;
                if(smaxn[v] > maxn[v])
                {
                    swap(smaxn[v], maxn[v]);
                    swap(smaxid[v], maxid[v]);
                }
            }
        }
        dfs2(v, u);
    }
}
int main()
{
    int n;
    int to, w;
    while(cin >> n)
    {
        init();
        for(int i = 2; i <= n; i++)
        {
            cin >> to >> w;
            add(i, to, w);
        }
        dfs1(1, -1);
        dfs2(1, -1);
        for(int i = 1; i <= n; i++)
            printf("%d\n", maxn[i]);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值