1. 程式人生 > >【題解】永無鄉 [HNOI2012] [BZOJ2733] [P3224]

【題解】永無鄉 [HNOI2012] [BZOJ2733] [P3224]

樹根 一個 lin printf 基本 沒有 get tchar 以及

【題解】永無鄉 [HNOI2012] [BZOJ2733] [P3224]


【題目描述】

永無鄉包含 \(n\) 座島,編號從 \(1\)\(n\) ,每座島都有自己的獨一無二的重要度,按照重要度可以將這 \(n\) 座島排名,名次用 \(1\)\(n\) 來表示。某些島之間由巨大的橋連接,通過橋可以從一個島到達另一個島。如果從島 \(a\) 出發經過若幹座(包括 \(0\) 座)橋可以到達島 \(b\) ,則稱島 \(a\) 和島 \(b\) 是連通的。

現在有兩種操作:

\(B\) \(x\) \(y\) \(:\) 表示在島 \(x\) 與島 \(y\) 之間修建一座新橋。
\(Q\)

\(x\) \(k\) \(:\) 表示詢問當前與島 \(x\) 連通的所有島中第 \(k\) 重要的是哪座島,即所有與島 \(x\) 連通的島中重要度排名第 \(k\) 小的島是哪座,請你輸出那個島的編號。

【輸入】

第一行兩個正整數 \(n\)\(m\) ,分別表示島的個數以及一開始存在的橋數。
接下來的一行有 \(n\) 個數,依次描述從島 \(1\) 到島 \(n\) 的重要度。
隨後的 \(m\) 行每行兩個正整數 \(a_i?\)\(b_i\)? ,表示一開始就存在一座連接島 \(a_i\) 和島 \(b_i\)? 橋。

後面第一行一個正整數 \(q\),表示一共有 \(q\)

個操作,接下來的 \(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]