树上最长路 sol

树上最长路


题目描述

给定一棵有 n n n个结点的树,树上每条边的长度为 w i w_i wi.

定义一棵树上的最长路为所有点对间,最短路最长的那对点间的最短路径长度。

在树中删去任意一条边,都会使得原树分为恰好两棵互相之间不连通的子树,现在 A A A君想知道删去每一条边后, 剩下的两棵子树中的最长路的较大值是多少。

为了方便起见,你只需要告诉 A A A 君删去每一条边后得到的最长路较大值的和。

输入格式

第一行一个整数 n n n,表示树的结点数

接下来 n − 1 n-1 n1行每行三个整数 u , v , w u,v,w u,v,w

表示一条连接 u , v u,v u,v的长度为 w w w的无向边

输出格式

仅一行一个整数表示答案

样例

input

5
2 1 7
3 1 7
4 2 5
5 2 6

output

63
数据范围

20 % 20\% 20%的数据: n ≤ 20 n\leq 20 n20

50 % 50\% 50%的数据: n ≤ 2000 n\leq 2000 n2000

另外 20 % 20\% 20%的数据:所有点度数均小于 4 4 4且最多只有一个点度数为 3 3 3

另有 20 % 20\% 20%的数据:树为随机生成,第 i i i条边连接 [ 1 , i ] [1,i] [1,i]间随机一点与点 i + 1 i+1 i+1
100 % 100\% 100%的数据: 1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1n105 1 ≤ w ≤ 1000 1\leq w\leq 1000 1w1000

sol:

对于前 50 % 50\% 50%的数据,都可以枚举删除每条边,直接做

下面考虑正解:

首先对于一个不在直径上的边,删除它之后对答案的贡献不变

tl8R3Q.png

对于此图,若我们删除 3 − 6 3-6 36这条边,则这次删除操作对于答案的没有影响,还是直径

若对于直径上的边,则答案就是以被删除的边的两个点为根分别求出两个联通快的最长链和次长链

然后就没了。。

具体细节看代码注释

Code:

#include<bits/stdc++.h>
using namespace std;
#define f1(a,b,c) for(ll c=a;c<=b;c++)
#define f2(a,b,c) for(int c=a;c>=b;c--)
#define f3(a,b,c) for(int c=a;c;c=b)
#define so1(a,n) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define re(a,n) reverse(a+1,a+n+1);
#define ll long long
#define itn int 
#define ubt int 
#define mp make_pair
#define pb push_back
#define pii pair<int,int>
#define pll pair<ll,ll>
#define DEBUG puts("FUCK");
const int twx=1e5+100;
const int inf=0x3f3f3f3f;
ll read()
{
    ll sum=0;
    ll flag=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
        {
            flag=-1;
        }
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        sum=((sum*10)+c-'0');
        c=getchar();
    }
    return sum*flag;
}
vector<pll> G[twx];
ll n;
ll d[twx];
ll X[twx];
ll Y[twx];
ll on[twx];
ll s;
ll t;
ll MAX[twx][3][2];
ll Deep[twx][2];
ll ans=0;
void get_diameter(ll x,ll fa)//求直径
{
    f1(0,(ll)G[x].size()-1,i)
    {
        ll y=G[x][i].first;
        ll z=G[x][i].second;
        if(y==fa)
        {
            continue ;
        }
        d[y]=d[x]+z;
        get_diameter(y,x);
    }
}
bool get_on_diameter(ll x,ll fa)//判断哪些点在直径上
{
    if(x==t)
    {
        on[x]=1;
        return 1;
    }
    f1(0,(int)G[x].size()-1,i)
    {
        ll y=G[x][i].first;
        if(y==fa)
        {
            continue ;
        }
        on[x]|=get_on_diameter(y,x);
    }
    return on[x];
}
void get_endpoint()//求直径的两个端点
{
    get_diameter(1,0);
    f1(1,n,i)
    {
        if(d[i]>d[s])
        {
            s=i;
        }
    }
    d[s]=0;
    get_diameter(s,0);
    f1(1,n,i)
    {
        if(d[i]>d[t])
        {
            t=i;
        }
    }
    on[s]=1;
}
void get_second_path(ll op,ll x,ll fa)
{
    Deep[x][op]=Deep[fa][op]+1;
    f1(0,(ll)G[x].size()-1,i)
    {
        ll y=G[x][i].first;
        ll z=G[x][i].second;
        if(y==fa)
        {
            continue ;
        }
        get_second_path(op,y,x);
        ll tmp=MAX[y][1][op]+z;
        if(tmp>MAX[x][1][op])
        {
            MAX[x][2][op]=MAX[x][1][op];
            MAX[x][1][op]=tmp;
        }
        else if(tmp>MAX[x][2][op])
        {
            MAX[x][2][op]=tmp;
        }
        MAX[x][0][op]=max(MAX[x][0][op],MAX[y][0][op]);
    }
    MAX[x][0][op]=max(MAX[x][0][op],MAX[x][1][op]+MAX[x][2][op]);
}
void init()
{
    n=read();
    f1(1,n-1,i)
    {
        ll x=read();
        ll y=read();
        ll z=read();
        G[x].pb(mp(y,z));
        G[y].pb(mp(x,z));
        X[i]=x;
        Y[i]=y;
    }
}
void work()
{
    get_on_diameter(s,0);
    get_second_path(0,s,0);
    get_second_path(1,t,0);
    f1(1,n-1,i)
    {
        ll XX=X[i];
        ll YY=Y[i];
        if(!on[XX]||!on[YY])//不在直径上的边贡献直接加
        {
            ans+=d[t];
            continue ;
        }
        if(Deep[XX][0]<Deep[YY][0])
        {
            ans+=max(MAX[XX][0][1],MAX[YY][0][0]);
        }
        else
        {
            ans+=max(MAX[XX][0][0],MAX[YY][0][1]);
        }
    }
}
void print()
{
    printf("%lld\n",ans);
}
int main()
{
    //freopen("tree.in","r",stdin);
    //freopen("tree.out","w",stdout);
    init();
    get_endpoint();
    work();
    print();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值