CF1294F 题解

Part.0 闲话

更好的观看体验

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

Part.1 题意

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

Part.2 分析

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

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

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

这时候,前文将边权设为 \(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)\),常数可能有一点大。

posted @ 2024-08-13 16:46  Yun_Mengxi  阅读(10)  评论(0编辑  收藏  举报