[并查集+启发式合并]BZOJ 2733——[HNOI2012]永无乡

Ps:又水听博主解释一下:昨天模拟赛遇到启发式的裸题,愣是打成左偏树的合并,惭愧惭愧。于是今天找到裸题练习一下。

题目梗概

有n个节点,每个节点有一个独一无二的权值。
合并一些节点。
询问一个节点所在联通块的权值第k大的节点,不存在则输出-1。

解题思路

什么SB题
这显然是平衡树的一道模板题,并且涉及到启发式合并。
启发式合并听着高大上其实是非常暴力过程。
当两棵平衡树合并时,把节点数较少的平衡树拆开,一个个插入到节点数较多的平衡树。
效率:最坏的情况是两棵树一样大,那么每个节点最多插入了 log2n 次。
博主写的是treap,两个 log
还有因为每次需要获取每个联通块的祖先,需要并查集搞一搞。

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100005;
struct jz{
    int x,r,s,id;
    jz* son[2];
};
jz a[maxn*100],*null=a,*til=a,*ro[maxn];
int n,m,f[maxn],Q;
inline int _read(){
    int num=0;char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') num=num*10+ch-48,ch=getchar();
    return num;
}
jz* newnode(int x,int id){
    til++;til->x=x;til->s=1;til->id=id;til->r=rand();
    til->son[0]=til->son[1]=null;
    return til;
}
void updata(jz* x){x->s=x->son[0]->s+x->son[1]->s+1;}
void turn(jz* &x,int d){
    jz* t=x->son[d];x->son[d]=t->son[d^1];t->son[d^1]=x;
    t->s=x->s;updata(x);x=t;
}
void Insert(jz* &x,int y,int id){
    if (x==null){x=newnode(y,id);return;}
    x->s++;
    if (y<x->x){
        Insert(x->son[0],y,id);
        if (x->son[0]->r<x->r) turn(x,0);
    }else{
        Insert(x->son[1],y,id);
        if (x->son[1]->r<x->r) turn(x,1);
    }
}
int kth(jz* x,int k){
    if (x->son[0]->s<k&&k<=x->son[0]->s+1) return x->id;else
    if (k<=x->son[0]->s) return kth(x->son[0],k);else
    return kth(x->son[1],k-x->son[0]->s-1);
}
void merge(jz* &x,jz* y){
    if (y==null) return;
    Insert(x,y->x,y->id);
    merge(x,y->son[0]);merge(x,y->son[1]);
}
int get(int x){
    if (f[x]==x) return x;
    f[x]=get(f[x]);
    return f[x];
}
void add(int x,int y){
    if (ro[x]->s<ro[y]->s) swap(x,y);
    if (x!=y) merge(ro[x],ro[y]),f[y]=x;
}
int main(){
    freopen("exam.in","r",stdin);
    freopen("exam.out","w",stdout);
    n=_read(),m=_read();
    for (int i=1;i<=n;i++) ro[i]=newnode(_read(),i),f[i]=i;
    for (int i=1;i<=m;i++){
        int x=get(_read()),y=get(_read());
        if (x||y) add(x,y);
    }
    Q=_read();
    while (Q--){
        char ch=getchar();
        while (ch!='Q'&&ch!='B') ch=getchar();
        if (ch=='Q'){
            jz* fa=ro[get(_read())];int y=_read();
            if (fa->s<y) printf("-1\n");else printf("%d\n",kth(fa,y));
        }else{
            int x=get(_read()),y=get(_read());
            add(x,y);

        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值