bzoj2733 [HNOI2012]永无乡(splay启发式合并)

bzoj2733 [HNOI2012]永无乡

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=2733

题意:
永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示。某些岛之间由桥连接。
现在有两种操作:
B x y 表示在岛 x 与岛 y 之间修建一座新桥。
Q x k 表示询问当前与岛 x连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪 座,请你输出那个岛的编号。

数据范围
n≤100000,m≤n,q≤300000

题解:
题意即维护连通块,支持查每个连通块的第k小,以及合并连通块。
维护第k小容易想到平衡树。
由于只有n个元素,最多只能合并n-1次,每次遍历较小集合,把元素插到较大集合(启发式合并)的复杂度是O(nlogn)。(启发式合并复杂度证明见这里
因此,加上平衡树插入的log,最终复杂度O(nlogn^2)。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N=100005;
int n,m,q;
queue<int> Q;
int val[N],father[N],ch[N][2],fa[N],root[N],size[N];
int getfa(int x)
{
    if(father[x]==x) return x;
    else return father[x]=getfa(father[x]);
}
void update(int x)
{
    size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
}
void rotate(int x,int &top)
{
    int y=fa[x]; int z=fa[y];
    if(y==top) top=x;
    else
    {if(ch[z][0]==y) ch[z][0]=x; else ch[z][1]=z;}
    fa[x]=z;
    int l=(ch[y][0]==x)? 0:1;  int r=l^1;
    ch[y][l]=ch[x][r]; fa[ch[x][r]]=y;
    ch[x][r]=y; fa[y]=x;
    update(y); update(x);
} 
void splay(int x,int &top)
{
    while(x!=top)
    {
        int y=fa[x]; int z=fa[y];
        if(y!=top)
        {
            if(ch[y][0]==x^ch[z][0]==y) rotate(x,top);
            else rotate(y,top);
        }
        rotate(x,top);
    }
}
void insert(int x,int &y)
{
    int tmp=y; int f=0;
    while(tmp)
    {
        if(val[x]<val[tmp]) {f=tmp; tmp=ch[tmp][0];}
        else {f=tmp; tmp=ch[tmp][1];}
    }
    if(val[x]<val[f]) ch[f][0]=x; else ch[f][1]=x;
    fa[x]=f;
    splay(x,y);
}
void merge(int fx,int fy)
{
    int x=root[fx]; int y=root[fy];
    if(size[x]>size[y]) {swap(x,y);swap(fx,fy);}
    father[fx]=fy;
    while(!Q.empty()) Q.pop();
    Q.push(x);
    while(!Q.empty())
    {
        int u=Q.front(); Q.pop();
        if(ch[u][0]) Q.push(ch[u][0]);
        if(ch[u][1]) Q.push(ch[u][1]);
        ch[u][0]=ch[u][1]=fa[u]=0;
        insert(u,root[fy]); 
    }
}
int find(int nd,int k)
{
    int ls=ch[nd][0]; int rs=ch[nd][1];
    if(size[nd]-size[rs]==k) return nd;
    if(size[ls]>=k) return find(ls,k);
    else return find(rs,k-size[nd]+size[rs]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {father[i]=root[i]=i;size[i]=1;}
    for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        int fx,fy; fx=getfa(x); fy=getfa(y);
        if(fx==fy) continue;
        merge(fx,fy);
    }
    scanf("%d",&q);
    while(q--)
    {
        char opt[5];
        scanf("%s",opt);
        if(opt[0]=='B')
        {
            int x,y;
            scanf("%d%d",&x,&y);
            int fx,fy;
            fx=getfa(x); fy=getfa(y);
            if(fx==fy) continue;
            merge(fx,fy);
        }
        else
        {
            int x,k;
            scanf("%d%d",&x,&k);
            int fx=getfa(x); int rt=root[fx];
            if(size[rt]<k) printf("-1\n");
            else printf("%d\n",find(rt,k)); 
        }
    }
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值