【題解】永無鄉 [HNOI2012] [BZOJ2733] [P3224]
【題解】永無鄉 [HNOI2012] [BZOJ2733] [P3224]
【題目描述】
永無鄉包含 \(n\) 座島,編號從 \(1\) 到 \(n\) ,每座島都有自己的獨一無二的重要度,按照重要度可以將這 \(n\) 座島排名,名次用 \(1\) 到 \(n\) 來表示。某些島之間由巨大的橋連接,通過橋可以從一個島到達另一個島。如果從島 \(a\) 出發經過若幹座(包括 \(0\) 座)橋可以到達島 \(b\) ,則稱島 \(a\) 和島 \(b\) 是連通的。
現在有兩種操作:
\(B\) \(x\) \(y\) \(:\) 表示在島 \(x\) 與島 \(y\) 之間修建一座新橋。
\(Q\)
【輸入】
第一行兩個正整數 \(n\) 和 \(m\) ,分別表示島的個數以及一開始存在的橋數。
接下來的一行有 \(n\) 個數,依次描述從島 \(1\) 到島 \(n\) 的重要度。
隨後的 \(m\) 行每行兩個正整數 \(a_i?\) 和 \(b_i\)? ,表示一開始就存在一座連接島 \(a_i\) 和島 \(b_i\)? 橋。
後面第一行一個正整數 \(q\),表示一共有 \(q\)
【輸出】
對於每個 \(Q\) \(x\) \(k\) 的操作依次輸出一行,其中包含一個整數,表示所詢問島嶼的編號。如果該島嶼不存在,則輸出 \(-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
2
5
1
2
【數據範圍】
\(20\%\) \(1 \leqslant n \leqslant 1000,1 \leqslant q \leqslant 1000\)
\(100\%\) \(1 \leqslant n \leqslant 1e5,m \leqslant n,1 \leqslant q \leqslant 3e5\)
【分析】
一道線段樹合並的好題
用並查集維護各個連通塊,每個塊都建立一棵權值線段樹,在合並兩個塊的同時,將它們的線段樹也進行合並。那麽原問題就變成了在一棵線段樹中求第 \(k\) 小,而這個是權值線段樹的基本操作。
\(【Code】\)
#include<algorithm>
#include<cstdio>
#define mid (L+R>>1)
#define pl tr[p].lp
#define pr tr[p].rp
#define Re register int
#define F(a,b) for(i=a;i<=b;++i)
using namespace std;
const int N=2e5+3;char c;
int x,y,i,n,m,cnt,f[N],id[N],pt[N];//pt[i]表示離散化後i這個位置所對應的權值樹根的編號
struct QAQ{int g,lp,rp;}tr[N<<5];//權值樹,保守開一個32*N
inline void in(Re &x){//【快讀】自己動手,豐衣足食...
x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
inline int build(Re L,Re R,Re x){//開一個空的權值樹
Re p=++cnt;++tr[p].g;//【動態開點】
if(L==R)return p;
if(x<=mid)pl=build(L,mid,x);
else pr=build(mid+1,R,x);
return p;
}
inline int merge(Re p,Re q){//【線段樹合並】
if(!p)return q;if(!q)return p;
//當需要合並的點的其中一個編號為0時 (即為空),返回另一個編號
tr[p].g+=tr[q].g,p;//把q合並到p上面去
pl=merge(pl,tr[q].lp);//分別合並左子樹,右子樹
pr=merge(pr,tr[q].rp);
return p;
}
inline int ask(Re p,Re L,Re R,Re k){//查詢
if(L==R)return id[R];//邊界:L==R
Re tmp=tr[pl].g;//計算左子樹共有多少個數字
if(tmp>=k)return ask(pl,L,mid,k);//左子樹已經超過k個,說明第k小在左子樹裏面
else return ask(pr,mid+1,R,k-tmp);//左子樹不足k個,應該在右子樹中找第(k-tmp)小
}
inline int find(Re x){if(x!=f[x])f[x]=find(f[x]);return f[x];}
int main(){
in(n),in(m);
F(1,n)in(x),id[x]=i,f[i]=i,pt[i]=build(1,n,x);
//用id[x]表示重要度為x的點的編號,給每個點建一棵樹
while(m--){//初始的橋要連起來
in(x),in(y),x=find(x),y=find(y);
merge(pt[x],pt[y]),f[y]=f[x];//註意這裏merge(pt[],pt[])和f[]=f[]中x,y的順序要相反
}
in(m);
while(m--){
scanf(" %c",&c),in(x),in(y),x=find(x);
if(c=='B')y=find(y),merge(pt[x],pt[y]),f[y]=f[x];
else{
if(tr[pt[x]].g<y)printf("-1\n");//如果總個數都小於y,說明沒有第y大的數,直接輸出-1
else printf("%d\n",ask(pt[x],1,n,y));
}
}
}
【題解】永無鄉 [HNOI2012] [BZOJ2733] [P3224]