「BZOJ 2733」「HNOI 2012」永无乡「启发式合并」
题意
你需要维护若干连通快,有两个操作
- 合并\(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;
}