洛谷P3304 [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≤2e5,所有点的编号都在 1∼N 的范围内,边的权值 ≤1e9。

解题思路

第一问求树的直径长度,我们可以直接套用模板,做两遍dfs。

第二问求所有直径经过的边的数量。那我们求出所有直径重合的这一段路径的边的数量,就是最终答案。

求法:

由于这条路径是在树上,所以只需要求出这条路径的两个端点即可,这端点就是各直径在这条重合路径同侧端点的LCA。

例子  在样例中有两条直径 3 1 4 2和3 1 4 6,3 1 4为重合路径,其左端只有端点3所以3为重合路径的左端点,其右端有2 6两个端点,所以求出2 6的LCA为4,节点4即为重合路径的右端点。再求出3 4 之间的路径条数即可。

我们可以定义两个数组dep(深度)和len(长度),dep表示其余节点与根节点之间的边条数,len表示其余节点与根节点之间的路径长度。第一遍dfs求出距根节点1最长的节点u;第二遍dfs以u(记u为一条直径的左端点)为根节点,找到所有直径的右端点,求出右端点们的LCA(记为v);第三遍dfs以v为根节点向左搜索,找到所有直径的左端点,求出左端点们的LCA(记为w)。此时以v为根节点的w的深度就是最终答案。

AC_Code

#include<bits/stdc++.h>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
typedef pair<ll, ll>PII;
vector<PII>g[N];
queue<int>b1, b2;
int dep[N], depu[N],depv[N], parent[N][20], parentu[N][20], parentv[N][20];
ll len[N], lenu[N], lenv[N];
void dfs(int x, int p, int dep[],ll len[], int parent[][20]) {
	for (int i = 1; i < 20; ++i) {
		parent[x][i] = parent[parent[x][i - 1]][i - 1];
	}
	dep[x] = dep[p] + 1;
	for (auto&y : g[x]) {
		if (y.first != p) {
			len[y.first] = len[x] + y.second;
			parent[y.first][0] = x;
			dfs(y.first, x, dep, len, parent);
		}
	}
}
//倍增求LCA
int lca(int a, int b, int dep[], int parent[][20]) {
	if (dep[a] < dep[b])swap(a, b);
	for (int i = 19; i >= 0; --i) {
		if (dep[a] - (1 << i) >= dep[b])
			a = parent[a][i];
	}
	if (a == b)return a;
	for (int i = 19; i >= 0; --i) {
		if (parent[a][i] != parent[b][i]) {
			a = parent[a][i];
			b = parent[b][i];
		}
	}
	return parent[a][0];
}
int main() {
	int n; cin >> n;
	for (int i = 1; i < n; ++i) {
		int a, b, c; cin >> a >> b >> c;
		g[a].push_back({ b,c });
		g[b].push_back({ a,c });
	}
	int u = 0, v = 0,w=0;
	dep[0] = -1;
	dfs(1, 0, dep,len,parent);
	for (int i = 1; i <= n; ++i)
		if (len[i] > len[u])u = i;
	depu[0] = -1;
	dfs(u, 0, depu, lenu,parentu);
	ll ans = *max_element(lenu + 1, lenu + 1 + n);
	for (int i = 1; i <= n; ++i)
		if (lenu[i] == ans) 
			b2.push(i);
		
	while (b2.size() != 1) {
		int a = b2.front(); b2.pop();
		int b = b2.front(); b2.pop();
		int c = lca(a, b,depu,parentu);
		b2.push(c);
	}
	v = b2.front();
	depv[0] = -1;
	dfs(v, 0, depv, lenv, parentv);
	ll ansv = *max_element(lenv + 1, lenv + 1 + n);
	for (int i = 1; i <= n; ++i)
		if (lenv[i] == ansv)b1.push(i);
	while (b1.size() != 1) {
		int av = b1.front(); b1.pop();
		int bv = b1.front(); b1.pop();
		int cv = lca(av, bv, depv, parentv);
		b1.push(cv);
	}
	w = b1.front();
	cout << ans << '\n';
	cout<<depv[w]<<'\n';
}

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值