[平衡树+启发式合并] 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 小的岛是哪 座,请你输出那个岛的编号。

题解

数据结构练手水题。
注意到只有合并操作,很容易想到对于每个联通块建平衡树,然后不断启发式合并。
据说splay启发式合并是nlogn?

#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
    int key,id,size; node* ch[2];
    node(int x=0,int y=0,node* son=NULL){ key=x; id=y; size=1; ch[0]=ch[1]=son; }
    int cmp(int k){
        if(key==k) return -1;
        return key<k?1:0;
    }
    void maintain(){ size=ch[0]->size+ch[1]->size+1; }
} nil, *null=&nil;
typedef node* P_node;
void init_null(){ null->size=0; null->ch[0]=null->ch[1]=null; }
void rot(P_node &p,int d){
    P_node k=p->ch[d^1]; p->ch[d^1]=k->ch[d]; k->ch[d]=p;
    p->maintain(); k->maintain(); p=k;
}
void splay(P_node &p,int tkey){
    int d1=p->cmp(tkey);
    if(d1!=-1){
        P_node p2=p->ch[d1]; int d2=p2->cmp(tkey);
        if(d2!=-1){
            splay(p2->ch[d2],tkey);
            if(d1==d2) rot(p,d1^1), rot(p,d2^1);
                  else rot(p->ch[d1],d2^1), rot(p,d1^1); 
        } else rot(p,d1^1);
    }
}
void Insert(P_node &p,int tkey,int id){
    if(p==null) p=new node(tkey,id,null);
           else Insert(p->ch[p->key<tkey],tkey,id);
    p->maintain();
}
int Kth(P_node p,int k){
    if(p->ch[0]->size==k-1) return p->id;
    if(p->ch[0]->size>=k) return Kth(p->ch[0],k);
    return Kth(p->ch[1],k-p->ch[0]->size-1);
}
const int maxn=100005;
int fa[maxn],w[maxn],c[maxn];
P_node rt[maxn];
void Print(P_node p){
    if(p==null) return;
    Print(p->ch[0]); c[++c[0]]=p->id; Print(p->ch[1]);
    delete p;
}
int getfa(int x){ return fa[x]==x?x:fa[x]=getfa(fa[x]); }

void Merge(int x,int y){
    if(getfa(x)==getfa(y)) return;
    P_node p1=rt[getfa(x)], p2=rt[getfa(y)];
    if(p1->size<p2->size) swap(p1,p2); 
    c[0]=0; Print(p2);
    for(int i=1;i<=c[0];i++) Insert(p1,w[c[i]],c[i]), splay(p1,w[c[i]]);
    x=getfa(x); y=getfa(y); rt[fa[x]=y]=p1;
}
int getint(){
    char ch=getchar(); int res=0,ff=1;
    while(!('0'<=ch&&ch<='9')){ if(ch=='-') ff=-1; ch=getchar(); }
    while('0'<=ch&&ch<='9') res=res*10+ch-'0', ch=getchar();
    return res*ff;
}
int n,m,Q;
int main(){
    freopen("bzoj2733.in","r",stdin);
    freopen("bzoj2733.out","w",stdout);
    init_null();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) w[i]=getint(), fa[i]=i, rt[i]=new node(w[i],i,null);
    for(int i=1;i<=m;i++){
        int x=getint(),y=getint();
        Merge(x,y);
    }
    scanf("%d",&Q);
    while(Q--){
        char ch=getchar(); while(ch!='B'&&ch!='Q') ch=getchar();
        int x=getint(),y=getint();
        if(ch=='B') Merge(x,y); else{
            if(rt[getfa(x)]->size<y) printf("-1\n"); else{
                int res=Kth(rt[getfa(x)],y); splay(rt[getfa(x)],w[res]);
                printf("%d\n",res);
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值