agc014_e.Blue and Red Tree 题解
题目链接:https://atcoder.jp/contests/agc014/tasks/agc014_e
思路
将同时存在于两棵树中的边称为公共边。
1. 公共边可以直接缩点
对于公共边 \(u-v\),添加对应红边时,只能删除蓝边 \(u-v\) 本身,因为原蓝树中 \(u\) 到 \(v\) 的唯一蓝色路径就是这条边。
这次操作可以推迟到最后:保留这条蓝边不会妨碍其他操作。因此,原问题等价于将两棵树中的 \(u,v\) 同时缩成一个点后得到的问题。
另一方面,任何合法方案的最后一次操作,只剩下一条蓝边,添加的红边必须与它相同。因此,存在合法方案时一定存在公共边。
于是可以不断执行:
- 找到任意公共边,将它的两个端点缩成一个点。
- 最终只剩一个点,输出
YES。 - 还有多个点但没有公共边,输出
NO。
2. 高效维护缩点
使用:
- 并查集维护每个点所在的分量。
vector<int> g[maxn]保存各分量包含的原始邻接记录。set<pair<int, int>> s保存当前分量之间的边,每对端点只保留一次。- 队列保存已经发现的公共边。
合并分量 \(x,y\) 时,将邻接表较短的 \(y\) 合并到 \(x\)。
对于原来连接 \(y,v\) 的边,将它改为 \(x,v\)。如果 \(x,v\) 已经存在,就发现了一条新的公共边。因为两棵树缩点后仍然分别是树,不可能出现同色重边。
邻接表可能包含重复或已经成为内部边的记录,利用并查集和 set 跳过即可。
复杂度
每条邻接记录每次移动后,所在邻接表的长度至少翻倍,所以最多移动 \(O(\log N)\) 次。
时间复杂度为 \(O(N\log^2 N)\),空间复杂度为 \(O(N)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n, cnt, fa[maxn];
vector<int> g[maxn];
set<pair<int, int>> s;
queue<pair<int, int>> q;
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
bool check() {
cnt = n;
while (!q.empty()) {
int x = find(q.front().first), y = find(q.front().second);
q.pop();
if (x == y) continue;
if (g[x].size() < g[y].size()) swap(x, y);
s.erase({min(x, y), max(x, y)});
fa[y] = x;
for (int v : g[y]) {
v = find(v);
if (v == x) continue;
if (!s.erase({min(y, v), max(y, v)})) continue;
if (!s.insert({min(x, v), max(x, v)}).second)
q.push({x, v});
}
g[x].insert(g[x].end(), g[y].begin(), g[y].end());
vector<int>().swap(g[y]);
cnt--;
}
return cnt == 1;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1, u, v; i <= 2 * n - 2; i++) {
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
if (u > v) swap(u, v);
if (!s.insert({u, v}).second)
q.push({u, v});
}
puts(check() ? "YES" : "NO");
return 0;
}
浙公网安备 33010602011771号