【题解】永无乡 [HNOI2012] [BZOJ2733] [P3224]

【题解】永无乡 [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));
        }
     }
}
posted @ 2019-05-02 21:09  辰星凌  阅读(247)  评论(0编辑  收藏  举报