洛谷P12504 「ROI 2025 Day1」树上的青蛙

在天狼星物理技术学院,你不仅能看到普通的青蛙,还能遇见会变色的树蛙!这些神奇的小家伙可以从绿色变成棕色,或者从棕色变回绿色,简直就像自然界的魔术师。

所谓树,是一个没有环的连通图。树上的每个节点都住着一只青蛙,初始时它们全是绿色的。青蛙们喜欢在树上跳来跳去,每次跳跃会从当前节点沿着边跳到旁边的节点。每跳一次,它们的颜色就会切换到相反的颜色。

青蛙们最爱成双成对地唱歌,但每一对必须由一只绿色和一只棕色的青蛙组成。为了组成一对,某只青蛙需要跳到另一只青蛙所在的节点,且跳跃次数不能超过 \(d\) 次。为了保证到达时颜色与对方不同,跳跃次数必须是奇数。

你的任务是找出最多能组成多少对青蛙合唱团。每只青蛙只能加入一对。如果能正确计算出最多的合唱团数量,你就能为子任务拿到部分分数。要想拿满分,你还得指出哪些青蛙应该配对,才能达到这个最大数量。

知识点:\(\textbf{DSU on Tree}\),贪心。

\(\textbf{Subtask 1}\) : \(n \le 14\)

考虑暴力的挑选出点对,写一个checker看看是否合法。

复杂度 \(O(2^n)\)

\(\textbf{Subtask 2}\) : \(n ≤ 300000,d=n−1\)

真是更简单了,既然 \(d=n-1\),说明全部都可以匹配。

那么我们直接通过奇偶层任意匹配就行了。

复杂度:\(O(n)\)

\(\textbf{Subtask 3,4,5,6,7,8}\)

在这里,我们需要深入思考了。

我们可以手玩一下样例,发现似乎有贪心。

这里引用 @Lucyna_Kushinada 的证明。

可以从下往上贪心,两个 set 在节点 u 启发式合并时,所模拟的路径是从其中一个 set 里的节点走到 u,再走到另一个 set 里的节点,如果路程为 D 或者当前在根节点,那就贪心计入答案。

考虑这么贪心为什么是对的,一对点匹配的贡献都是相同的 1 是前提,而贪心是有顺序的从下往上,显然越晚(路程越靠近 D)配对越好,这说明 u 以下的节点不能让他们这样走的路程为 D,即使往上走还可能出现距离刚好为 D 的点,实际上还不如偏安一隅,往上走浪费的不仅是自己的机会,还有别的点的机会,下面也没有能和你配的了,还能怎么办不必多说。

复杂度 \(O(nd \log n)\)

注意到 \(d\) 是很小的。

可以通过,\(64 pts\)

\(\huge \mathscr{Code}\)

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 100;
int n, d, siz[N], dep[N], hson[N], cnt[N], vis[N];
vector<int> g[N];
set<pair<int, int>> S[2];
vector<pair<int, int>> ans;
void dfsget(int u, int fath) {
	siz[u] = 1;
	dep[u] = dep[fath] + 1;
	for (int e : g[u]) {
		if (e == fath) continue;
		dfsget(e, u);
		siz[u] += siz[e];
		if (!hson[u] or siz[e] > siz[hson[u]]) hson[u] = e;
	}
}
void add(int u, int fath, int del) {
	if (!vis[u]) {
		S[dep[u] & 1].insert(make_pair(dep[u], u));
	}
	for (int e : g[u]) {
		if (e == fath or e == del) continue;
		add(e, u, del);
	}
}
void clear(int u, int fath) {
	if (!vis[u]) S[dep[u] & 1].erase(make_pair(dep[u], u));
	for (int e : g[u]) {
		if (e == fath) continue;
		clear(e, u);
	}
}
void calc(int rt) {
	vector<pair<int, int>> del;
	for (auto e : S[0]) {
		auto [depu, u] = e;
		if (depu > dep[rt] + d) {
			del.push_back(e);
			continue;
		}
		auto it = S[1].upper_bound(make_pair(d + 2 * dep[rt] - depu, N));
		if (it == S[1].begin()) continue;
		--it;
		auto [depv, v] = *it;
		if (rt != 1 && d + 2 * dep[rt] - depu != depv) continue;
		vis[u] = vis[v] = 1;
		S[1].erase(it);
		del.push_back(e);
		ans.push_back(make_pair(u, v));
	}
	for (auto e : del) S[0].erase(e);
}
void DSU(int u, int fath) {
	for (int e : g[u]) {
		if (e == fath or e == hson[u]) continue;
		DSU(e, u);
		clear(e, u);
	}
	if (hson[u]) DSU(hson[u], u);
	add(u, fath, hson[u]);
	calc(u);
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> d;
	if (!(d & 1)) d--;
	for (int i = 1; i < n; i++) {
		int x, y;
		cin >> x >> y;
		g[x].push_back(y), g[y].push_back(x);
	}
	dfsget(1, 0);
	DSU(1, 0);
	cout << ans.size() << '\n';
	for (auto e : ans) cout << e.first << ' ' << e.second << '\n';
	return 0;
}

\(\textbf{Subtask all}\)

实际上,我们可以敏锐的发现,上面的代码是过不了 \(2\) 号点的,因为 \(d\) 太大了。

考虑我们的贪心,既然要在刚好等于 \(d\) 的时候匹配,每次收集子树信息是不是太浪费了。

假设现在有一个点 \(u\),深度记作 \(dep_u\)

找到最大的满足 \(d_u+d_v−2 \times d_{lca}≤d\) 且奇偶性与 \(d_u\) 相反的 \(d_v\),计算出他们可以完美合并(路程刚好为 \(d\))的时候的深度,然后等到此深度的时候再看能不能计入答案。

题解从略。

\(\huge \mathscr{Code}\)

#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
#define push_back emplace_back
using namespace std;
const int N = 5e5 + 100;
int n, D, siz[N], dep[N], hson[N];
double anss;
vector<int> g[N];
vector<pair<int, int>> ans;
struct node {
	int lim = 0;
	map<int, vector<int>> s[2];
	priority_queue<pair<int, int>> q;
	void Clear() {
		s[0].clear(), s[1].clear();
		while (q.size()) q.pop();
	}
	void Update() {
		lim--;
		for (auto i : { 0,1 })
			while (s[i].size()) {
				auto it = (--s[i].end());
				if (it->first > lim + D) s[i].erase(it);
				else break;
			}
	}
	void Find(int d) {
		
		int z = (d & 1) ^ 1;
		auto it = s[z].upper_bound(D + 2 * lim - d);
		if (it == s[z].begin()) return;
		--it;
		int lim2 = (it->first + d - D) / 2;
		int tmpd = ((d & 1) ? it->first : d);
		q.push(make_pair(lim2, tmpd));
	}
	void Insert(int d, int x) {
		if (s[d & 1].find(d) != s[d & 1].end()) s[d & 1][d].push_back(x);
		else s[d & 1][d].push_back(x), Find(d);
	}
	void Match() {
		while (q.size()) {
			
			auto [fi, se] = q.top();
			if (lim > fi) break;
			q.pop();
			
			int d1 = se, d2 = D + 2 * lim - d1;
			if (s[0].find(d1) == s[0].end()) continue;
			if (s[1].find(d2) == s[1].end()) continue;
			int u = s[0][d1].back(), v = s[1][d2].back();
			s[0][d1].pop_back(), s[1][d2].pop_back();
			ans.push_back(make_pair(u, v));
			if (s[0][d1].empty()) s[0].erase(d1);
			if (s[1][d2].empty()) s[1].erase(d2);
			auto it = s[0].upper_bound(d1);
			if (it != s[0].begin()) { --it, Find(it->first); }
			it = s[1].upper_bound(d2);
			if (it != s[1].begin()) { --it, Find(it->first); }
		}
	}
}S[N];
void dfsget(int u, int fath) {
	siz[u] = 1;
	dep[u] = dep[fath] + 1;
	for (int e : g[u]) {
		if (e == fath) continue;
		dfsget(e, u);
		siz[u] += siz[e];
		if (!hson[u] or siz[e] > siz[hson[u]]) hson[u] = e;
	}
}
void DSU(int u, int fath) {
	if (!hson[u]) {
		S[u].lim = dep[u];
		S[u].Insert(dep[u], u);
		return;
	}
	DSU(hson[u], u);
	swap(S[u], S[hson[u]]);
	S[u].Update();
	S[u].Insert(dep[u], u);
	for (int e : g[u]) {
		if (e == fath or e == hson[u]) continue;
		DSU(e, u);
		for (int i : {0, 1}) {
			for (auto z : S[e].s[i]) for (int x : z.second) S[u].Insert(z.first, x);
		}
		S[e].Clear();
	}
	S[u].Match();
}
set<pair<int, int>> lastS[2];
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> D;
	if (!(D & 1)) D--;
	for (int i = 1; i < n; i++) {
		int x, y;
		cin >> x >> y;
		g[x].push_back(y), g[y].push_back(x);
	}
	dfsget(1, 0);
	DSU(1, 0);
	for (int i : {0, 1}) {
		for (auto z : S[1].s[i]) for (int x : z.second) lastS[i].insert(make_pair(-dep[x], x));
	}
	for (auto z : lastS[0]) {
		auto [depu, u] = z;
		depu = -depu;
		auto it = lastS[1].lower_bound(make_pair(-D - 2 * dep[1] + depu, 0));
		if (it == lastS[1].end()) continue;
		auto [depv, v] = *it;
		depv = -depv;
		lastS[1].erase(it);
		ans.push_back(make_pair(u, v));
	}
	cout << ans.size() << '\n';
	for (auto e : ans) cout << e.first << ' ' << e.second << '\n';
	return 0;
}
posted @ 2025-08-03 10:27  OrangeRED  阅读(28)  评论(0)    收藏  举报