[SDOI2013]直径(树的直径)

[SDOI2013]直径

题目描述

小Q最近学习了一些图论知识。根据课本,有如下定义。树:无回路且连通的无向图,每条边都有正整数的权值来表示其长度。如果一棵树有N个节点,可以证明其有且仅有N-1 条边。

路径:一棵树上,任意两个节点之间最多有一条简单路径。我们用 dis(a,b)表示点a和点b的路径上各边长度之和。称dis(a,b)为a、b两个节点间的距离。

直径:一棵树上,最长的路径为树的直径。树的直径可能不是唯一的。

现在小Q想知道,对于给定的一棵树,其直径的长度是多少,以及有多少条边满足所有的直径都经过该边。

输入输出格式

输入格式:

第一行包含一个整数N,表示节点数。 接下来N-1行,每行三个整数a, b, c ,表示点 a和点b之间有一条长度为c的无向边。

输出格式:

共两行。第一行一个整数,表示直径的长度。第二行一个整数,表示被所有直径经过的边的数量。

输入输出样例

输入样例#1: 复制

6
3 1 1000
1 4 10
4 2 100
4 5 50
4 6 100

输出样例#1: 复制

1110
2

说明

【样例说明】 直径共有两条,3 到2的路径和3到6的路径。这两条直径都经过边(3, 1)和边(1, 4)。

对于100%的测试数据:2<=N<=200000,所有点的编号都在1..N的范围内,边的权值<=10^9。



题解


这道题目比较玄学。
我们知道对一颗树不止一条直径,那么被所有直径经过的边,
随着直径的分叉而不断收缩。
而直径的分叉点一定是在直径上的。
但是我并不知道为什么要从起点推一遍收缩的一段,再从树的直径的末尾推一遍收缩的另一端。这两段之间的路径就是解。
但是一遍循环是推不出的,必须循环两次??
如果有巨佬一个循环搞出了两个段点可以告诉本蒟蒻qwq。




代码


#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N=200001;
ll dis[N],maxx,s,t;
ll n,m,num,head[N],vis[N];
struct node{
    int to,nex;
    ll v;
}e[N<<1];
ll dep[N],ff[N],l,r,ans,son[N];
void add(int from,int to,ll v){
    num++;
    e[num].to=to;
    e[num].v=v;
    e[num].nex=head[from];
    head[from]=num;
}

ll read(){
    ll x=0,w=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*w;
}

void dfs(int x,int fa){
    for(int i=head[x];i;i=e[i].nex){
        int v=e[i].to;if(v==fa)continue;ff[v]=x;
        dis[v]=dis[x]+e[i].v;dfs(v,x);
    }
}

void dfs2(int u,int fa){
    dep[u]=0;ll maxn=0;
    for(int i=head[u];i;i=e[i].nex){
        int v=e[i].to;if(v==ff[u]||vis[v]==1)continue;
        dfs2(v,u);maxn=max(maxn,dep[v]+e[i].v);
    }
    dep[u]=maxn;    
}

int main(){
    n=read();
    for(int i=1;i<n;i++)
    {
        ll x=read(),y=read(),z=read();
        add(x,y,z);add(y,x,z);
    }
    dfs(1,0);
    for(int i=1;i<=n;i++){if(dis[i]>maxx)maxx=dis[i],s=i;dis[i]=0;}
    dfs(s,0);maxx=0;
    for(int i=1;i<=n;i++)if(dis[i]>maxx)maxx=dis[i],t=i;
    printf("%lld\n",maxx);
    l=t;r=s;
    int now=t;
    while(now!=s){
        vis[now]=1;son[ff[now]]=now;now=ff[now];
    }
    now=t;
    while(now!=s){
        dep[now]=0;
        dfs2(now,0);
        if(dep[now]==maxx-dis[now])l=now;
        now=ff[now];
    }
    now=s;
    while(now){
        dfs2(now,0);
        if(dep[now]==dis[now])r=now;
        now=son[now];
    }
    while(l!=r&&l){
        l=ff[l];ans++;
    }
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/hhh1109/p/9525018.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值