This blog is YMx's. Please follow me. qwq|

园龄:粉丝:关注:

CF1294F 题解

Part.0 闲话

更好的观看体验

目前题解区里大多数巨佬都是采用的树形 dp 和暴力等方法,看见没有我这种做法,欢迎指出做法问题或 hack 代码。

Part.1 题意

给定一棵树,选 3 个点 a,b,c,求 ab 的路径与 ac 的路径与 bc 的路径上一共有多少不重复的边。

Part.2 分析

我们不妨把每条边的边权设为 1,这样方便我们计算贡献。

仔细思考一下后,可以发现无论如何,先把树的直径的端点选上最优,而 c 也一定是在某一个距离直径上点最远的点。

但是一个问题诞生了,怎么求这个距离最远的点呢?枚举直径上点再搜的时间复杂度是 O(n2) 的,肯定会超时。

这时候,前文将边权设为 1 的好处就凸显出来了。

我们可以把所有直径上的边的边权全部设置为 0,然后从直径的某一个端点再次搜索一遍树的直径,那么新直径的另一个端点就是我们要求的 c,起点可以选在直径上的任意一点,因为此时在直径上搜不会增加答案的值。

所以答案就是旧直径的长加新直径的长。

Part.3 代码

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 2e5 + 5;

struct Node {
	int v, w, nxt;
} e[kMaxN << 1];

int n;
int dis, x;
int res, a, b, c;
int hd[kMaxN], cnt = 1;
vector<pair<int, int>> stk, d;

void add(int u, int v) {
	e[++cnt].nxt = hd[u];
	hd[u] = cnt;
	e[cnt].v = v;
	e[cnt].w = 1;
}

void dfs(int u, int fa, int s) {
	if (s >= dis) {
		dis = s;
		if (u != a && u != b && u != c) {
			x = u;
			d = stk;
		}
	}
	for (int i = hd[u]; i; i = e[i].nxt) {
		int v = e[i].v, w = e[i].w;
		if (v == fa) {
			continue;
		}
		stk.push_back({u, i});
		dfs(v, u, s + w);
		stk.pop_back();
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 1, a, b; i < n; i++) {
		cin >> a >> b;
		add(a, b);
		add(b, a);
	}
	dis = 0;
	dfs(1, 0, 0);
	a = x;
	dis = 0;
	dfs(x, 0, 0);
	b = x;
	res += dis;
	dis = 0;
	for (auto i : d) {
		e[i.second].w = e[i.second ^ 1].w = 0;
	}
	dfs(x, 0, 0);
	c = x;
	res += dis;
	cout << res << '\n' << a << ' ' << b << ' ' << c << '\n';
	return 0;
}

值得注意的是,一定要使用链式前向星并且把双向的边全部边权赋为 0。因为我太菜了,所以只能开个栈储存直径上的边,事实上可以边搜边赋值。

时间复杂度 O(n),常数可能有一点大。

本文作者:Yun_Mengxi

本文链接:https://www.cnblogs.com/Yun-Mengxi/p/18357275

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Yun_Mengxi  阅读(13)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起