【题解】永无乡 [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));
}
}
}