[Gym101194G] Pandaria 题解

发布时间 2023-12-14 00:43:32作者: MoyouSayuki

[Gym101194G] Pandaria 题解

题目描述

给定一张无向图,边有边权,点有颜色 \(\le 10^6\),每次询问给定 \(x, w\),表示 Mr. Panda 从 \(x\) 出发,可以选定一个颜色 \(c\),使得在不走 \(> w\) 的边的情况下,能到达颜色为 \(c\) 的点的数量最大,输出这个 \(c\)

强制在线。

\(n \le 10^5, m\le 2\times 10^5\)

思路

看到强制在线和最大瓶颈路不难想到 Kruskal 重构树,在重构树上考虑问题,设点权不超过 \(w\) 且点权尽可能大的点为 \(u\),询问 \(u\) 子树内颜色出现次数的最大的颜色,

这个子树统计问题很明显可以用 DSU on Tree(树上启发式合并)或线段树合并做。

为了锻炼码力我写了线段树合并。

具体而言,对于每一个实点开一个动态开点的权值线段树,节点维护区间出现次数最多的颜色个数和颜色编号,可以 \(pushup\),然后在建立虚点的时候合并左右两个儿子的线段树即可。

时间复杂度:\(O(n\log n)\)\(n, m\) 视为同级。

#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
// #define int long long
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 2e5 + 10, M = 8e6 + 10;

int n, m, c[N];
struct qwq {
    int a, b, c;
    bool operator < (const qwq &W) const {
        return c < W.c;
    }
} e[N];
int root[N], ls[M], rs[M], idx, maxc;
PII dat[M];
PII max(PII a, PII b) {
    if(a.x == b.x) return (a.y < b.y ? a : b);
    return (a.x > b.x ? a : b);
}
int update(int u, int l, int r, int x) {
    if(!u) u = ++ idx;
    if(l == r) return dat[u].x ++, dat[u].y = l, u;
    int mid = l + r >> 1;
    if(x <= mid) ls[u] = update(ls[u], l, mid, x);
    else rs[u] = update(rs[u], mid + 1, r, x);
    dat[u] = max(dat[ls[u]], dat[rs[u]]);
    return u;
}
int merge(int a, int b, int l, int r) {
    if(!a || !b) return a + b;
    if(l == r) return dat[a].x += dat[b].x, a;
    int mid = l + r >> 1;
    ls[a] = merge(ls[a], ls[b], l, mid);
    rs[a] = merge(rs[a], rs[b], mid + 1, r);
    dat[a] = max(dat[ls[a]], dat[rs[a]]);
    return a;
}
int fa[N], tot, f[N][20], cw[N];
PII p[N]; 
vector<int> g[N];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
void Kruskal() {
    sort(e + 1, e + m + 1);
    for(int i = 1; i <= 2 * n; i ++)
        g[i].clear();
    tot = n;
    for(int i = 1, a, b; i <= m; i ++) {
        a = e[i].a, b = e[i].b;
        int x = find(a), y = find(b);
        if(x == y) continue;
        g[++ tot].push_back(x), g[tot].push_back(y);
        root[tot] = merge(root[x], root[y], 1, maxc);
        p[tot] = dat[root[tot]];
        cw[tot] = e[i].c;
        fa[x] = fa[y] = fa[tot] = tot;
    }
}
void dfs(int u, int fat) {
    f[u][0] = fat;
    for(int i = 1; i <= 19; i ++)
        f[u][i] = f[f[u][i - 1]][i - 1];
    for(auto v : g[u])
        dfs(v, u);
}
int jump(int u, int w) {
    for(int i = 19; i >= 0; i --)
        if(f[u][i] && cw[f[u][i]] <= w)
            u = f[u][i];
    return u;
}

void work(int T) {
    cin >> n >> m;
    maxc = 0;
    for(int i = 1; i <= n; i ++) cin >> c[i], fa[i] = i, maxc = max(maxc, c[i]);
    memset(ls, 0, sizeof ls), memset(rs, 0, sizeof rs), idx = 0, memset(root, 0, sizeof root), memset(dat, 0, sizeof dat);
    for(int i = 1; i <= n; i ++) root[i] = update(root[i], 1, maxc, c[i]), p[i] = dat[root[i]];
    for(int i = 1, a, b, c; i <= m; i ++) {
        cin >> a >> b >> c;
        e[i] = {a, b, c};
    }
    Kruskal(), dfs(tot, 0);
    int q, lst = 0;
    cin >> q;
    cout << "Case #" << T << ":\n";
    while(q --) {
        int x, w;
        cin >> x >> w;
        x ^= lst, w ^= lst;
        cout << (lst = p[jump(x, w)].y) << '\n';
    }
    
    return ;
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T = 1; 
    cin >> T;
    for(int i = 1; i <= T; i ++) work(i);

    return 0;
}

突发奇想:注意到重构树上的实点编号是连续的,可不可以利用 dfn 转化为区间最大值查询问题呢?Sparse Table 明显比线段树合并好写。

有机会写一下吧。