bzoj 2733 [HNOI2012]永无乡

传送门


Sol

  • 一眼看上去是水题,没想到跪了一上午。。。
  • 看到第k小想到平衡树,维护连通性用并查集。因此主要思路就是维护一个平衡树森林,合并操作就把两个平衡树合并
  • 难点在于,合并暴力肯定TLE,因此需要启发式合并
void dfs(int p,int &f){
    if(lc) dfs(lc,f);
    if(rc) dfs(rc,f);
    int x,y;
    split(f,val[p],x,y);
    renode(p);
    f=merge(merge(x,p),y);
}
  • 代码是fhq treap。虽然就是把一颗平衡树拆掉再一个一个加到另一颗树里。但是启发式合并的巧妙在于,它是把节点数小的树拆掉合并到大的树上,于是均摊复杂度就成了优雅的 O(nlogn)
  • 合并的时候还有一些细节:节点信息的清空和并查集的更新

Code

// by spli
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<ctime>
#define lc ch[p][0]
#define rc ch[p][1]
using namespace std;

const int N=100010*2;
int n,m,q,sz;
int c[N],pos[N];
int lnk[N];
int siz[N];
int col[N],rt;
int ch[N][2];
int pri[N];
int val[N];

int _find(int x){
    if(x!=lnk[x]) lnk[x]=_find(lnk[x]);
    return lnk[x];
}

int newnode(int id){
    sz++;
    siz[sz]=1;
    val[sz]=c[id];
    pos[sz]=id;
    pri[sz]=rand();
    return sz;
}

void pushup(int p){
    siz[p]=1;
    if(lc) siz[p]+=siz[lc];
    if(rc) siz[p]+=siz[rc];
}

int merge(int x,int y){
    if(!x||!y) return x+y;
    if(pri[x]<pri[y]){
        ch[x][1]=merge(ch[x][1],y);
        pushup(x);
        return x;
    }
    else{
        ch[y][0]=merge(x,ch[y][0]);
        pushup(y);
        return y;
    }
}

void split(int p,int v,int &x,int &y){
    if(!p){
        x=0,y=0;
        return;
    }
    if(val[p]<=v) x=p,split(rc,v,rc,y);
    else y=p,split(lc,v,x,lc);
    pushup(p);
}

int getmin(int p){
    while(1){
        if(lc) p=lc;
        else return val[p];
    }
}

void renode(int p){
    lc=rc=0;
}

void dfs(int p,int &f){
    if(lc) dfs(lc,f);
    if(rc) dfs(rc,f);
    int x,y;
    split(f,val[p],x,y);
    renode(p);
    f=merge(merge(x,p),y);
}

void DFS(int p){
    if(lc) DFS(lc);
    cout<<pos[p]<<" ";
    if(rc) DFS(rc);
}

void un(int x,int y){
    int r[2];
    r[0]=_find(x);
    r[1]=_find(y);
    if(r[0]==r[1]) return;
    if(siz[r[0]]<siz[r[1]]) swap(r[0],r[1]);
    dfs(r[1],r[0]);
    lnk[_find(x)]=lnk[_find(y)]=r[0];
    lnk[r[0]]=r[0];
}

int get_th(int p,int k){
    while(1){
        if(k<=siz[lc]) p=lc;
        else if(k==siz[lc]+1) return p;
        else k-=siz[lc],k--,p=rc;
    }
}

void query(int a,int k){
    int rt=_find(a);
    if(siz[rt]<k) puts("-1");
    else printf("%d\n",pos[get_th(rt,k)]);
}

int main(){
    srand(19260817);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%d",&c[i]);
        //ins(i,c[i]);
        col[i]=newnode(i);
    }
    for(int i=1;i<=n;++i) lnk[i]=i;
    int a,b;
    while(m--) scanf("%d%d",&a,&b),un(a,b);
    scanf("%d",&q);
    int x,y,c,d;
    char op[10];
    while(q--){
        scanf("%s",op);
        scanf("%d%d",&x,&y);
        if(op[0]=='B') un(x,y);
        if(op[0]=='Q') query(x,y);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值