[HDU] 2196 Computer

http://acm.hdu.edu.cn/showproblem.php?pid=2196

求树上每点为起点的最长路径。

 

upd:最远点一定在直径的一个端点上,所以随便找一个直径,两个端点两次dfs即可(哭)

 

耐心分析一下,一个点开始的最长路径,也就两种情况。

一个是走子树,一个是走父亲那边,显然的。

设f[i]为从点i出发走子树的最长路径,g[i]同理,为走父亲的最长路径,也就是不经过子树任何一个点的最长路径

考虑转移:f可以通过一次dfs直接转移,f[x]=f[v]+e[i].w

g转移就不能直接转移了,首先,g[1]=0,因为定这棵树的根为1了,不经过任何一个点的路径,当然是0。

考虑从点x向儿子v转移,分两个情况。

1.f[v]+e[i].w==f[x],也就是v在x向下的最长路径上

2.v不在x向下的最长路径上

把第一类v找出来,记为最大的儿子(树剖既视感),特殊处理。

先做完剩下的v,既然它们不参与构成x向下的最长路径,它们就可以复用f[x](x向下的最长路径)和g[x](x向上的最长路径),有转移

g[v]=max(f[x],g[x])+e[i].w

然后做那个最大的v,它参与构成了f[x],考虑它向上的路径,无非有两种。

一种是从x向上走,也就是g[x],另一种是g向下走,走次大的f[v]+e[i].w,这个次大se可以在第一类转移时顺便记录。

于是有转移g[v]=max(g[x],se)+mxe,其中mxe是v向上的那条边,它连接了v和x。

这就做完啦,几个小细节:多组数据,清空f和g(一开始以为下一次dfs就覆盖了,但事实是每次树的形态不同,虽然根一样,但是dfs顺序可能有差别,会错误复用一些f和g)

#include<iostream>
#include<cstring>
#include<cstdio>
#define int long long
using namespace std;

inline int rd() {
    int ret=0,f=1;
    char c;
    while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
    while(isdigit(c))ret=ret*10+c-'0',c=getchar();
    return ret*f;
}

const int MAXN=10005;

struct Edge {
    int next,to,w;
} e[MAXN<<1];
int ecnt,head[MAXN];
inline void add(int x,int y,int w) {
    e[++ecnt].next = head[x];
    e[ecnt].to = y;
    e[ecnt].w = w;
    head[x] = ecnt;
}

int n;
int f[MAXN],g[MAXN];

void F(int x,int pre) {
    for(int i=head[x]; i; i=e[i].next) {
        int v=e[i].to;
        if(v==pre) continue;
        F(v,x);
        f[x]=max(f[x],f[v]+e[i].w);
    }
}

void G(int x,int pre) {
    int mx=0,mxid=0,mxe=0;
    for(int i=head[x]; i; i=e[i].next) {
        int v=e[i].to;
        if(v==pre) continue;
        if(mx<f[v]+e[i].w) {
            mx=f[v]+e[i].w;
            mxid=v;
            mxe=e[i].w;
        }
    }
    mx=max(g[x],f[x]);
    int se=0;
    for(int i=head[x]; i; i=e[i].next) {
        int v=e[i].to;
        if(v==pre||v==mxid) continue;
        g[v]=e[i].w+mx;
        se=max(se,f[v]+e[i].w);
    }
    g[mxid]=max(g[x],se)+mxe;
    for(int i=head[x]; i; i=e[i].next) {
        int v=e[i].to;
        if(v==pre) continue;
        G(v,x);
    }
}

signed main() {
    while(~scanf("%lld",&n)) {
        ecnt=0;
        memset(head,0,sizeof(head));
        memset(f,0,sizeof(f));
        memset(g,0,sizeof(g));
        int x,y;
        for(int i=2; i<=n; i++) {
            x=rd();
            y=rd();
            add(i,x,y);
            add(x,i,y);
        }
        F(1,0);
        G(1,0);
        for(int i=1; i<=n; i++) printf("%lld\n",max(f[i],g[i]));
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/ghostcai/p/9332583.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值