[树的直径] 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。

题解

题目传送门:SDOI2013
这道题思路不难,代码也简单(真的不是一般的难调)
讲一下思路吧,

对于第一问,两遍dfs/bfs/dp就出来了,但是由于我们后面还需要用到直径这条路径,所以用dfs/bfs比较好

第二问
如何判断一条边是否是直径上的必须边
我们知道,树的直径长度只有一个,但不只一条
设一条直径上一个点u,我们发现若以u为根的子树,不包括直径上的节点,离它最远的点距离与直径上的端点离它的距离一样,这说明有另一条直径
首先,我们看下面一幅图

可以看到2-9-8-5-6-1是它的一条直径
但是仔细观察就可以发现,它不只这一条直径

我们看到上面这幅图,3-5-6-1或者是3-5-6-4-10也是一条

所以我们可以保存直径的两个端点,然后左端点向右跳,右端点向左跳,看着条路径上有没有满足上述条件的点,如果有,就把左短点/右端点移到这个点

Code

#include<bits/stdc++.h>
#define rg register
#define Min(a,b) (a)<(b)?(a):(b)
#define Max(a,b) (a)>(b)?(a):(b)

using namespace std;
typedef long long lol;

const int N=200010;

void in(int &ans)
{
  ans=0;int f=1;char i=getchar();
  while(i<'0' || i>'9') {if(i=='-') f=-1;i=getchar();}
  while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+(i^48),i=getchar();
  ans*=f;
}

int n,m,cnt,u,v,ans,B;
int nex[N<<1],to[N<<1],head[N],w[N<<1];
int son[N],vis[N],fa[N],dep[N];
lol dis[N],f[N];

inline void add(int a,int b,int c)
{
  to[++cnt]=b,nex[cnt]=head[a];
  w[cnt]=c,head[a]=cnt;
}

void dfs(int u)
{
  for(int i=head[u];i;i=nex[i])
    if(to[i]!=fa[u])
      fa[to[i]]=u, dep[to[i]]=dep[u]+1,dis[to[i]]=dis[u]+w[i], dfs(to[i]);
}

void find(int u)
{
  f[u]=dis[u];
  for(int i=head[u];i;i=nex[i])
    if(!vis[to[i]] && to[i]!=fa[u])
      find(to[i]),f[u]=Max(f[u],f[to[i]]);
}

void Pre()
{
  int x=fa[v];
  while(x) {
    find(x);
    if(f[x]==dis[B]) v=x;
    x=fa[x];
  }
}

void Next()
{
  int x=son[u];
  while(x) {
    find(x);
    if(f[x]-dis[x]==dis[x]) u=x;
    x=son[x];
  }
}

void init()
{
  dfs(1);
  for(int i=1;i<=n;i++) if(dis[i]>dis[u]) u=i;
  memset(dep,0,sizeof(dep));
  memset(fa,0,sizeof(fa));
  memset(dis,0,sizeof(dis));
  dfs(u);
  for(int i=1;i<=n;i++) if(dis[i]>dis[v]) B=v=i;
  printf("%lld\n",dis[v]);
  int x=v; while(x) vis[x]=1,son[fa[x]]=x,x=fa[x];
}

int main()
{
  int a,b,c;in(n);
  for(int i=1;i<n;i++) {
    in(a),in(b),in(c);
    add(a,b,c),add(b,a,c);
  }
  init(); Next(); Pre();
  printf("%d\n",dep[v]-dep[u]);
}

博主蒟蒻,随意转载.但必须附上原文链接

http://www.cnblogs.com/real-l/

原文地址:https://www.cnblogs.com/real-l/p/9516188.html

时间: 2024-11-09 08:49:20

[树的直径] SDOI2013 直径的相关文章

SDOI2013 直径(树的直径必经边)

SDOI2013 直径 题目传送 sol: 先求出任一直径同时把直径拎出来,树的非直径部分全部挂在直径上(如下). 对于直径上的每一个点i,如果存在它到非直径上点的最大距离\(g[i]\)等于它到直径两端点中较短的那一段\(d[i]\), 则说明这一段也可以成为直径中的一部分. 而我们需要得到所有直径的交,画图可以发现假设两端(以中点为界)都存在上述的点,最逼近的两点间的边即为所求! 具体可以看代码实现. code: #include<bits/stdc++.h> #define IL inl

P3304 [SDOI2013]直径(【模板】树直径的必经边)

题目地址 基本思路: 题目要求树直径的必经边,那么首先应当获取一条直径. 获取直径后从直径上的两个端点分别遍历一次直径,每次遍历直径时从直径上的每个点分别dfs一次并不经过直径上的点,如果深度可以被替换则说明非必经边. #include<cstdio> #include<iostream> #include<queue> #include<cstring> #define ll long long using namespace std; const int

Bzoj 3124: [Sdoi2013]直径 题解

3124: [Sdoi2013]直径 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 1222  Solved: 580[Submit][Status][Discuss] Description 小Q最近学习了一些图论知识.根据课本,有如下定义.树:无回路且连通的无向图,每条边都有正整数的权值来表示其长度.如果一棵树有N个节点,可以证明其有且仅有N-1 条边. 路径:一棵树上,任意两个节点之间最多有一条简单路径.我们用 dis(a,b)表示点a和点

[Sdoi2013] 直径

[Sdoi2013]直径 时间限制: 1 Sec  内存限制: 256 MB 题目描述 小Q最近学习了一些图论知识.根据课本,有如下定义.树:无回路且连通的无向图,每条边都有正整数的权值来表示其长度.如果一棵树有N个节点,可以证明其有且仅有N-1 条边. 路径:一棵树上,任意两个节点之间最多有一条简单路径.我们用 dis(a,b)表示点a和点b的路径上各边长度之和.称dis(a,b)为a.b两个节点间的距离.   直径:一棵树上,最长的路径为树的直径.树的直径可能不是唯一的. 现在小Q想知道,对

3124: [Sdoi2013]直径

3124: [Sdoi2013]直径 https://www.lydsy.com/JudgeOnline/problem.php?id=3124 分析: 所有直径都经过的边,一定都是连续的一段.(画个图,反证一下) 然后可以求出一条直径后,可以对每个点求出不经过直径到达的最远的距离. 然后判断一下,找到左边分叉的最后一个,右边分叉的第一个,中间的点就是所有直径都经过的点. 代码: 1 #include<bits/stdc++.h> 2 using namespace std; 3 typede

[算法模版]树的重心和直径

[算法模版]树的重心和直径 树的重心 引自OI-WIKI 定义 以树的重心为根时,所有的子树(不算整个树自身)的大小都不超过整个树大小的一半. 找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心. 删去重心后,生成的多棵树尽可能平衡. 性质 树中所有点到某个点的距离和中,到重心的距离和是最小的:如果有两个重心,那么他们的距离和一样. 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两个树的重心的路径上. 在一棵树上添加或删除一个叶子,那么它的重心最多只移动

[Sdoi2013]直径(树的直径)

//36分 #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #include<algorithm> #include<map> using namespace std; typedef long long ll; #define setfire(name) freopen(#name".in","r",st

[SDOI2013]直径 (树的直径,贪心)

题目链接 Solution 我们直接找到一条直径 \(s\),起点为 \(begin\),终点为 \(end\). 从前往后遍历点 \(u\) ,若子树中最大的距离与 \(dis(u,begin)\) 相等. 很显然这个点不在公共线段上,很显然可以用子树的中的一段接上,形成一条新的直径. 然后从后往前遍历,同样的道理... 然后找到两个节点 \(l,r\) 然后答案即为 \(r-l\). 记得要开 \(longlong\). Code #include<bits/stdc++.h> #defi

【Luogu P3304】[SDOI2013]直径

题目链接 题意,求一棵树被所有直径经过的边的条数. 这题是我们8.25KS图论的最后一题,当时我果断打了暴力求所有直径然后树上差分统计的方法,好像有点小问题,boom0了. 考完改这题,改了好久,各种各样的小bug,至少有七八个... 思路:先随便找一条直径,然后从一个端点开始遍历这条直径,如果当前点能分叉出一条直径,那么这条直径后面的点都不可能被所有直径穿过了,于是我们找到第一个能分叉的点,然后再从这个点往回遍历,找到第一个能分叉的点,这2个点中间的路径的边即为所求. 代码就算了吧,打这题时我