[HNOI2012] 永無鄉
洛谷題目鏈接:永無鄉
題目描述
永無鄉包含 \(n\) 座島,編號從 \(1\) 到 \(n\) ,每座島都有自己的獨一無二的重要度,按照重要度可以將這 \(n\) 座島排名,名次用 \(1\) 到 \(n\) 來表示。某些島之間由巨大的橋連接,通過橋可以從一個島到達另一個島。如果從島 \(a\) 出發經過若幹座(含 \(0\) 座)橋可以 到達島 \(b\) ,則稱島 \(a\) 和島 \(b\) 是連通的。
現在有兩種操作:
B x y 表示在島 \(x\) 與島 \(y\) 之間修建一座新橋。
Q x k 表示詢問當前與島 \(x\) 連通的所有島中第 \(k\) 重要的是哪座島,即所有與島 \(x\)
輸入輸出格式
輸入格式:
第一行是用空格隔開的兩個正整數 \(n\) 和 \(m\) ,分別表示島的個數以及一開始存在的橋數。
接下來的一行是用空格隔開的 \(n\) 個數,依次描述從島 \(1\) 到島 \(n\) 的重要度排名。隨後的 \(m\) 行每行是用空格隔開的兩個正整數 \(a_i\)? 和 \(b_i\) ,表示一開始就存在一座連接島 \(a_i\)? 和島 \(b_i\)? 的橋。
後面剩下的部分描述操作,該部分的第一行是一個正整數 \(q\) ,表示一共有 \(q\) 個操作,接下來的 \(q\) 行依次描述每個操作,操作的 格式如上所述,以大寫字母 \(Q\)
輸出格式:
對於每個 Q x k 操作都要依次輸出一行,其中包含一個整數,表示所詢問島嶼的編號。如果該島嶼不存在,則輸出 \(-1\) 。
輸入輸出樣例
輸入樣例#1:
5 1
4 3 2 5 1
1 2
7
Q 3 2
Q 2 1
B 2 3
B 1 5
Q 2 1
Q 2 4
Q 2 3
輸出樣例#1:
-1
2
5
1
2
說明
對於 20% 的數據 \(n \leq 1000, q \leq 1000\)
對於 100% 的數據 \(n \leq 100000, m \leq n, q \leq 300000\)
一句話題意:
題解: 連通很顯然是可以用並查集維護的,那麽我們考慮如何查詢第k小.這裏我們需要一個方便維護的數據結構,可以使用splay+啟發式合並來維護一個連通塊.
什麽是啟發式合並?其實說簡單點就是將一個元素個數少的並查集加入到另一個並查集中.
我們可以先構建\(n\)顆splay,用1 ~ n的編號表示1 ~ n 的splay的根(就和只有一顆splay時以0作為默認根是一個道理),以n+1 ~ n+n作為樹中的元素,然後合並的時候就判斷兩個元素是否已經在同一個並查集中,如果不在,就把元素少的那一顆splay合並到另一顆splay上,每個點最多被合並logn次,所以這個復雜度是沒問題的(雖然這裏我也不清楚為什麽是logn的) .
然後就是在模擬合並的時候變量搞清楚,就沒什麽問題了.
#include<bits/stdc++.h>
using namespace std;
const int N=500000+5;
int n, m, ask, w[N];
int cnt = 0, root[N], fa[N], pos[N];
//root[i]表示第i顆splay的根節點
struct splay{
int ch[2], fa, size, val;
}t[N];
int gi(){
int ans = 0, f = 1; char i = getchar();
while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();}
while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
return ans * f;
}
int find(int x){return x==fa[x] ? x : fa[x]=find(fa[x]);}
bool get(int x){return x == t[t[x].fa].ch[1];}
void up(int x){t[x].size = t[t[x].ch[0]].size+t[t[x].ch[1]].size+1;}
void rotate(int x){
int fa = t[x].fa, gfa = t[fa].fa, d1 = get(x), d2 = get(fa);
t[t[x].ch[d1^1]].fa = fa; t[fa].ch[d1] = t[x].ch[d1^1];
t[fa].fa = x; t[x].ch[d1^1] = fa;
t[x].fa = gfa; t[gfa].ch[d2] = x;
up(fa), up(x);
}
void splay(int x, int goal){
while(t[x].fa != goal){
int fa = t[x].fa, gfa = t[fa].fa;
int d1 = get(x), d2 = get(fa);
if(gfa != goal){
if(d1 == d2) rotate(fa);
else rotate(x);
}
rotate(x);
}
if(goal <= n) root[goal] = x;//goal屬於1~n時是設置的根節點
}
void insert(int val, int rt){
int node = root[rt], fa = rt;
while(node && t[node].val != val)
fa = node, node = t[node].ch[t[node].val<val];
node = ++cnt;
if(fa > n) t[fa].ch[t[fa].val<val] = node;
t[node].fa = fa, t[node].val = val, t[node].size = 1;
splay(node, rt);
}
int kth(int rt, int k){
if(k > t[root[rt]].size) return -1;
int node = root[rt];
while(1){
int son = t[node].ch[0];
if(k <= t[son].size) node = son;
else if(k > t[son].size+1){
k -= t[son].size+1;
node = t[node].ch[1];
}
else return t[node].val;
}
}
void remove(int node, int to){
insert(t[node].val, to);
if(t[node].ch[0]) remove(t[node].ch[0], to);
if(t[node].ch[1]) remove(t[node].ch[1], to);
}
void merge(int x, int y){
int r1 = find(x), r2 = find(y);
if(r1 == r2) return;
if(t[root[r1]].size > t[root[r2]].size) swap(r1, r2);//r1 is smaller
fa[r1] = r2; remove(root[r1], r2);
}
int main(){
//freopen("data.in","r",stdin);
char opt; int x, y; n = gi(); m = gi(); cnt = n*2;
for(int i=1;i<=n;i++){
w[i] = gi(), fa[i] = i, root[i] = i+n;//以n+1~n+n作為splay的元素,一開始為根
pos[w[i]] = i; t[i+n].val = w[i];
t[i+n].size = 1; t[i+n].fa = i;
}
for(int i=1;i<=m;i++) x = gi(), y = gi(), merge(x, y);
ask = gi();
while(ask--){
cin >> opt; x = gi(); y = gi();
if(opt == 'B') merge(x, y);
else{
int temp = kth(find(x), y);
if(temp != -1) printf("%d\n", pos[temp]);
else printf("-1\n");
}
}
return 0;
}
[HNOI2012] 永無鄉