HDU 2196 题解

题意简述

给一个树(带权值,给定方法:第iii行两个数v,wv,wv,w,分别表示iii接到哪个点和边长),请依次输出从i(1&lt;=i&lt;=n)i(1&lt;=i&lt;=n)i(1<=i<=n)点开始能到的最长路长度。

数据

输入:
5
1 1//这是第2行,所以表示2和1之间有一个长度为1的边
2 1//同理,这个表示3—2[边长=1]
3 1//这个表示4–3[边长=1]
1 1//这个表示5–1[边长=1]

5
1 1
2 1
3 1
1 1

输出
3
2
3
4
4

思路

每个点开始暴力DFSDFSDFSO(n)O(n)O(n)的(以每个点为根,记录深度,求最大值,O(n)O(n)O(n)处理),然后每次都要换点,就是O(n2)O(n^2)O(n2)了。
那么,我们想几个O(n)O(n)O(n)的情况、
1.O(1)O(1)O(1)转换,使得我们在枚举iii点的时候不用每次都新算,而是珂以继承,就O(n)O(n)O(n)了。
似乎不珂行。。。
2.O(n)O(n)O(n)求出所有答案,也就是把所有的点一块跑DPDPDP。这样总共也是O(n)O(n)O(n)的。
这个仿佛珂以,但如果不尝试的话,就真的没有办法了。
如果要这样搞,首先必须要有一个根,设1为根。
根据套路,dp[i]dp[i]dp[i]表示以iii为根能走到的最大长度。显而易见地dp[i]=max{dp[son]+dis(son,i)}dp[i]=max\{dp[son]+dis(son,i)\}dp[i]=max{dp[son]+dis(son,i)},也就是max{所有儿子的max\{所有儿子的max{dp值加上到儿子的距离}值加上到儿子的距离\}}。这实在是很好理解,不细讲。
但此时我们会发现,这样还不够。又不是所有点都一定只从下面经过就是最长路。看下面这个图就知道了。
blog1.png
如果这个时候,我们以红色点为起点,然后还只是考虑这个点以下的答案(即绿色部分),就不正确了。我们应该还要考虑蓝色的部分,也就是从iii点上面经过的最长路径。设这个值为dp[i][1]dp[i][1]dp[i][1],刚刚的是dp[i][0]dp[i][0]dp[i][0]。不难发现dp[i][1]dp[i][1]dp[i][1]就等于(i到父亲的距离+父亲到一个不经过i点的叶节点最长距离)。
我们知道边界条件dp[1][1]=0dp[1][1]=0dp[1][1]=0,因为111是根节点。

那么,如何转移呢?

关键就在于第二部分,也就是父亲到一个不经过iii点的最长距离。大概想想,好像只要找一个离iii父亲最远的叶节点走过去就是最远了(1),或者是直接继承iii父亲往上走的最大值(2)(这个要是有肯定毫不犹豫就继承了,过会会讲如何实现的)。(2)情况也就是dp[father][1]dp[father][1]dp[father][1],这个没什么难度。

但(1)情况不大全面,要分类一下。如果iii的父亲往下走的最长路要经过iii,就不能直接找最长了(不然走重了),珂怜的iii就只能继承iii父亲的第二长路了。

否则的情况iii就珂以继承iii父亲的第一长路。到此,我们就对这个dp[i][1]dp[i][1]dp[i][1]的转移差不多明白个大概了。但由于这个的思维难度实在是很大,所以看不懂也没有关系,要有耐心。

讲几个代码实现中要注意的问题:

1.由于dp[i][1]dp[i][1]dp[i][1]直接由父亲转移我不会写,所以我写的版本是由iiiiii的儿子作转移的方式
2.转移dp[i][1]dp[i][1]dp[i][1]的时候,设max1,v1max1,v1max1,v1分别为最大长度,此时经过的儿子。max2,v2max2,v2max2,v2分别为第二大长度,此时经过的儿子。对于uuu点的儿子vvv,如果v==v1v==v1v==v1,说明uuu点的最长路要从vvv经过,所以此时只能选第二大,即dp[v][1]=max2+wdp[v][1]=max2+wdp[v][1]=max2+w,其中www表示uuuvvv的距离。
否则(即v!=v1v!=v1v!=v1的情况),vvv就珂以选最长路了,此时dp[v]=max1+wdp[v]=max1+wdp[v]=max1+w
还有一个是刚刚留下的FlagFlagFlag:如何记录dp[u][1]dp[u][1]dp[u][1]的值?
如果dp[u][1]&gt;max1dp[u][1]&gt;max1dp[u][1]>max1,说明dp[u][1]dp[u][1]dp[u][1]是比所有往下走的路都要优的解,那么我们将max2,v2max2,v2max2,v2设置为max1,v1max1,v1max1,v1(即一次滑动操作),令max1=dp[u][1]max1=dp[u][1]max1=dp[u][1],把v1v1v1设置为−1-11。那么我们在考虑儿子vvv的时候,无论如何也不会==v1==v1==v1,此时就一定珂以继承v1v1v1的值,也就是dp[u][1]dp[u][1]dp[u][1]
否则,如果dp[u][1]==max1dp[u][1]==max1dp[u][1]==max1或者dp[u][1]&gt;max2dp[u][1]&gt;max2dp[u][1]>max2,说明dp[u][1]dp[u][1]dp[u][1]虽然不能作为最优解,但是珂以作为第二优的解。用和上面类似的方法更新max2,v2max2,v2max2,v2的值,当v==v1v==v1v==v1的时候,就珂以来继承这个答案了。

好了,来看看代码吧:

#include<bits/stdc++.h>
#define N 101000
#define LogN 21
#define int long long
using namespace std;
class Graph//奇怪的存图
{
    private:
        int head[N];
        int EdgeCount;
    public:
        struct Edge
        {
            int To,Label,Next;
        }Ed[N<<1];
        void clear()
        {
            memset(head,-1,sizeof(head));
            memset(Ed,-1,sizeof(Ed));
            EdgeCount=0;
        }
        void AddEdge(int u,int v,int w)//directed
        {
            EdgeCount++;
            Ed[EdgeCount]=(Edge){v,w,head[u]};
            head[u]=EdgeCount;
        }

        int Start(int u)
        {
            return head[u];
        }

        int Next(int u)
        {
            return Ed[u].Next;
        }
        int To(int u)
        {
            return Ed[u].To;
        }
        int Label(int u)
        {
            return Ed[u].Label;
        }
}G;

int n;
void Add(int u,int v,int w)//加一个带权无向边
{
    G.AddEdge(u,v,w);G.AddEdge(v,u,w);
}
void Input()
{
    for(int i=2;i<=n;i++)
    {
        int v,w;
        scanf("%lld%lld",&v,&w);
        Add(i,v,w);
    }
}

int dp[N][2];
//dp[i][0/1]:如上解释
void DFS1(int u,int fa)//求出所有dp[i][0]的值
{
    dp[u][0]=0;
    for(int i=G.Start(u);~i;i=G.Next(i))
    {
        int v=G.To(i);
        if (v!=fa)
        {
            DFS1(v,u);
            dp[u][0]=max(dp[u][0],dp[v][0]+G.Label(i));
        }
    }
}
void DFS2(int u,int f)//求出所有dp[i][1]的值
{
    int max1=0,v1=-1;
    int max2=0,v2=-1;//和上面一样
    for(int i=G.Start(u);~i;i=G.Next(i))
    {
        int v=G.To(i),w=G.Label(i);
        if (v!=f)
        {
            int tmp=dp[v][0]+w;//到叶节点的距离
            if (tmp>max1)//说明这是最优解
            {
                max2=max1;v2=v1;//滑动一次
                max1=tmp;v1=v;//更新最优
            }
            else if (tmp==max1 or tmp>max2)//说明这是次优解
            {
                max2=tmp;
                v2=v;//更新次优
            }
        }
    }
    if (u!=1)//说明u不是根节点
    {
        int tmp=dp[u][1];//那么dp[u][1]就是有值的
        if (tmp>max1)
        {
            max2=max1;v2=v1;
            max1=tmp,v1=-1;
        }
        else if (tmp==max1 or tmp>max2)//这个上面讲了
        {
            max2=tmp;v2=-1;
        }
    }
    for(int i=G.Start(u);~i;i=G.Next(i))//转移
    {
        int v=G.To(i),w=G.Label(i);
        if (v!=f)
        {
            if (v==v1)
            {
                dp[v][1]=max2+w;
            }
            else
            {
                dp[v][1]=max1+w;
            }
            DFS2(v,u);
        }
    }
}


main()
{
    while(~scanf("%lld",&n))//处理输入(相比上面的思维要简单的多吧。。。)
    {
        G.clear();
        Input();
        memset(dp,0,sizeof(dp));
        DFS1(1,0);
        DFS2(1,0);
        for(int i=1;i<=n;i++) printf("%lld\n",max(dp[i][0],dp[i][1]));
    }
    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值