AtCoder Beginner Contest 350 G Solution

AT_abc350_g Mediator

省流

强制在线求森林中与两个节点有直接连边的节点。

赛时不知道怎么利用森林的特性然后没有想出来。

思路

由于保证操作途中图始终为森林,所以可以为每一个节点确认一个唯一的父亲。

为方便,在初始时所有节点的父亲都为 \(0\)

显然,若与两个节点 \(u\)\(v\) 同时存在直接连边的节点存在,只有三种可能。

  1. \(u\)\(v\) 的爷爷。
  2. \(v\)\(u\) 的爷爷。
  3. \(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);
    }
}
posted @ 2024-04-21 15:22  丝羽绫华  阅读(87)  评论(0编辑  收藏  举报