「BZOJ 2733」「HNOI 2012」永无乡「启发式合并」

时间:2021-12-06 20:42:45

题意

你需要维护若干连通快,有两个操作

  • 合并\(x,y\)所在的连通块
  • 询问\(x\)所在连通块中权值从小到大排第\(k\)的结点编号

题解

可以启发式合并\(splay\),感觉比较好些的

一个连通块就是一个\(splay\),每次合并挑小的\(splay\)遍历一遍把点按中序遍历存下来,然后一个一个插入大的\(splay\)就行了;查询就是\(splay\)\(kth\)操作

这样时间复杂度\(O(n \log n)\),它的证明可以见2018论文集 :董炜隽《浅谈Splay与Treap的性质及其应用》,其中有提一个\(\text{Dynamic Finger Theorem}\)

(其实随便插入的话是两个\(\log\),也能通过,十分玄学)

#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;

const int N = 2e5 + 10;

int n, m, q, bel[N], rt[N], w[N];
int ch[N][2], fa[N], sz[N];

void upd(int u) { sz[u] = sz[ch[u][0]] + sz[ch[u][1]] + 1; }
int dir(int u) { return ch[fa[u]][1] == u; }

void rotate(int u) {
    int d = dir(u), f = fa[u];
    if(fa[u] = fa[f]) ch[fa[u]][dir(f)] = u;
    if(ch[f][d] = ch[u][d ^ 1]) fa[ch[f][d]] = f;
    fa[ch[u][d ^ 1] = f] = u;
    upd(f); upd(u);
}
void ins(int &rt, int u, int f = 0) {
    if(!rt) {
        rt = u; fa[u] = f; sz[u] = 1;
        ch[u][0] = ch[u][1] = 0;
        return ;
    }
    ins(ch[rt][w[u] > w[rt]], u, rt);
    upd(rt);
}
void splay(int u) {
    for(; fa[u]; rotate(u)) if(fa[fa[u]])
        rotate(dir(u) == dir(fa[u]) ? fa[u] : u);
    rt[bel[u]] = u;
}
int kth(int u, int k) {
    int v = u;
    while(1) {
        if(k <= sz[ch[v][0]]) v = ch[v][0];
        else {
            k -= sz[ch[v][0]] + 1;
            if(k <= 0) break ;
            v = ch[v][1];
        }
    }
    splay(v);
    return v;
}

int a[N], l;
void dfs(int u) {
    if(u) {
        dfs(ch[u][0]);
        a[++ l] = u;
        dfs(ch[u][1]);
    }
}

void link(int u, int v) {
    u = bel[u]; v = bel[v];
    if(u == v) return ;
    if(sz[rt[u]] < sz[rt[v]]) swap(u, v);
    l = 0; dfs(rt[v]);
    for(int i = 1; i <= l; i ++) {
        bel[a[i]] = u;
        ins(rt[u], a[i]);
        splay(a[i]);
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++) scanf("%d", w + i);
    for(int i = 1; i <= n; i ++) {
        bel[i] = rt[i] = i; sz[i] = 1;
    }
    int u, v;
    for(int i = 1; i <= m; i ++) {
        scanf("%d%d", &u, &v);
        link(u, v);
    }
    scanf("%d", &q);
    char op[5];
    for(int i = 1; i <= q; i ++) {
        scanf("%s%d%d", op, &u, &v);
        if(* op == 'Q') {
            u = rt[bel[u]];
            if(v > sz[u]) puts("-1");
            else printf("%d\n", kth(u, v));
        }
        if(* op == 'B') link(u, v);
    }
    return 0;
}