AtCoder Beginner Contest 350 G Solution
AT_abc350_g Mediator
省流
强制在线求森林中与两个节点有直接连边的节点。
赛时不知道怎么利用森林的特性然后没有想出来。
思路
由于保证操作途中图始终为森林,所以可以为每一个节点确认一个唯一的父亲。
为方便,在初始时所有节点的父亲都为 \(0\) 。
显然,若与两个节点 \(u\) 和 \(v\) 同时存在直接连边的节点存在,只有三种可能。
- \(u\) 是 \(v\) 的爷爷。
- \(v\) 是 \(u\) 的爷爷。
- \(u\) 和 \(v\) 是兄弟且他们的父亲不是 \(0\) 。
由此询问 \(O(1)\) 解决。主要时间瓶颈在于连接操作。
显然,在题目给出的条件下,一旦两个独立连通块被连接,其中一个连通块内的所有节点的上下级关系都必然发生改变,即需要全部重新确定父亲。
可以采用启发式合并的思想,使用带权并查集判断重构哪一个连通块所用时间更少。
由此,总合并复杂度为 \(O(n\log n)\) ,总时间复杂度为 \(O(n\log n+q)\) 。
代码
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10, mod = 998244353;
using ll = long long;
int n, q, f[N], fa[N], siz[N];
ll a, b, c, pr;
vector<int> road[N];
void dcpt(ll &x, ll &y, ll &z)
{
x = 1 + x * (pr + 1) % mod % 2;
y = 1 + y * (pr + 1) % mod % n;
z = 1 + z * (pr + 1) % mod % n;
}
void dfs(int x)
{
for (auto &i : road[x])
{
if (i == f[x])
continue;
f[i] = x;
dfs(i);
}
}
int query(int x, int y)
{
if (f[x] == f[y] and f[x])
return f[x];
if (f[f[x]] == y)
return f[x];
if (f[f[y]] == x)
return f[y];
return 0;
}
inline int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
inline void merge(int x, int y)
{
int fx = find(x), fy = find(y);
if (fx == fy)
return;
fa[fy] = fx;
siz[fx] += siz[fy];
siz[fy] = 0;
}
int main()
{
cin >> n >> q;
for (int i = 1; i <= n; i++)
fa[i] = i, siz[i] = 1;
for (int i = 1; i <= q; i++)
{
cin >> a >> b >> c;
dcpt(a, b, c);
// fprintf(stderr, "%lld %lld %lld\n", a, b, c);
if (a == 2)
{
pr = query(b, c);
cout << pr << '\n';
continue;
}
if (siz[find(b)] < siz[find(c)])
swap(b, c);
road[b].push_back(c);
road[c].push_back(b);
f[c] = b;
merge(b, c);
dfs(c);
}
}