[BZOJ2733][HNOI2012] 永无乡(线段树合并)
题面
永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛 到达另一个岛。如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连 通的。现在有两种操作:B x y 表示在岛 x 与岛 y 之间修建一座新桥。Q x k 表示询问当前与岛 x连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪 座,请你输出那个岛的编号。
分析
乍一眼看上去像平衡树的合并
但实际上用权值线段树合并即可
用并查集维护连通性,同时对于每个联通块维护一个权值线段树,权值线段树维护的序列的第i个位置为1表示当前联通块里有权值为i的节点,线段树维护区间和,代表联通块里值为[l,r]之间的数的个数
合并的时候就直接合并两个联通块对应的线段树节点
查询的时候类似在线段树上二分,每次根据区间和决定向左子树还是右子树递归。如果左子树的和>=rank,则说明第rank重要的节点在右子树里
代码
#include<iostream>
#include<cstdio>
#define maxn 100005
#define maxlogn 18
using namespace std;
int n,m,q;
int a[maxn];
int id[maxn];
int fa[maxn];
int find(int x){
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
void merge_set(int x,int y){
fa[find(x)]=find(y);
}
int ptr=0;
int root[maxn*maxlogn];
struct node{
int ls;
int rs;
int val;
}tree[maxn*maxlogn];
void push_up(int pos){
tree[pos].val=tree[tree[pos].ls].val+tree[tree[pos].rs].val;
}
void insert(int &pos,int l,int r,int val){
if(pos==0) pos=++ptr;
if(l==r){
tree[pos].val=1;
return;
}
int mid=(l+r)>>1;
if(val<=mid) insert(tree[pos].ls,l,mid,val);
else insert(tree[pos].rs,mid+1,r,val);
push_up(pos);
}
int query(int pos,int l,int r,int val){
if(l==r) return l;
int mid=(l+r)>>1;
if(tree[tree[pos].ls].val>=val) return query(tree[pos].ls,l,mid,val);
else return query(tree[pos].rs,mid+1,r,val-tree[tree[pos].ls].val);//记得减掉左子树的和
}
int merge_tree(int x,int y){
if(!x) return y;
if(!y) return x;
tree[x].ls=merge_tree(tree[x].ls,tree[y].ls);
tree[x].rs=merge_tree(tree[x].rs,tree[y].rs);
push_up(x);
return x;
}
int main(){
int u,v;
char cmd[2];
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
scanf("%d %d",&u,&v);
merge_set(u,v);
}
for(int i=1;i<=n;i++){
insert(root[find(i)],1,n,a[i]);
id[a[i]]=i;
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
scanf("%s",cmd);
if(cmd[0]=='Q'){
scanf("%d %d",&u,&v);
if(tree[root[find(u)]].val<v) printf("-1\n");
else printf("%d\n",id[query(root[find(u)],1,n,v)]);
}else{
scanf("%d %d",&u,&v);
int fu=find(u),fv=find(v);
if(fu!=fv){
root[fv]=merge_tree(root[fu],root[fv]);//是把fu并到fv
fa[fu]=fv;
}
}
}
}
版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢