Live2d Test Env

BZOJ- 2733: 永无乡 (并查集&线段树合并)

题意:给定N个节点,K次操作,操作有两种,1是合并两个集合,2是求某个集合的第K大(从小到大排序)。

思路:合并只要启发式即可。此题可以用线段树,保存1到N的排序的出现次数和。 复杂度O(NlogN)。想象一下,当其中一棵树节点少的时候,复杂度是O(logN)的,次数不超过N次;当两棵树的节点都蛮多的时候,复杂度是O(N)的,但是这样的合并能使得集合变得很大,显然这样的合并次数非常少,小于logN次。

所以合并线段树的总复杂度就算O(NlogN),每次询问K大的操作是线段树常规操作,单词复杂度是logN的

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int maxn=4000010;
int rt[maxn],ls[maxn],rs[maxn],fa[maxn],cnt;
int a[maxn],id[maxn],sum[maxn];
int find(int u){
    if(fa[u]!=u) fa[u]=find(fa[u]);
    return fa[u];
}
void insert(int &Now,int L,int R,int pos)
{
    if(!Now) Now=++cnt; sum[Now]++;
    if(L==R) return ;int Mid=(L+R)>>1;
    if(pos<=Mid) insert(ls[Now],L,Mid,pos);
    else insert(rs[Now],Mid+1,R,pos);
}
int merge(int x,int y){
    if(!x) return y;
    if(!y) return x;
    ls[x]=merge(ls[x],ls[y]);
    rs[x]=merge(rs[x],rs[y]);
    sum[x]=sum[ls[x]]+sum[rs[x]];
    return x;
}
int query(int x,int L,int R,int num){
    if(L==R) return L; int Mid=(L+R)>>1;
    if(sum[ls[x]]>=num) return query(ls[x],L,Mid,num);
    return query(rs[x],Mid+1,R,num-sum[ls[x]]);
}
int main()
{
    int N,M,K,u,v; char opt[4];
    scanf("%d%d",&N,&M);
    rep(i,1,N) scanf("%d",&a[i]),id[a[i]]=i;
    rep(i,1,N) fa[i]=i;
    rep(i,1,M) {
        scanf("%d%d",&u,&v);
        int p=find(u),q=find(v);
        if(p!=q) fa[p]=q;
    }
    scanf("%d",&K);
    rep(i,1,N) insert(rt[find(i)],1,N,a[i]);
    rep(i,1,K){
        scanf("%s%d%d",opt,&u,&v);
        if(opt[0]=='B'){
           int p=find(u),q=find(v);
           if(p!=q){
              fa[q]=p;
              rt[p]=merge(rt[p],rt[q]);
           }
        }
        else {
            int p=find(u);
            if(sum[rt[p]]<v) puts("-1");
            else printf("%d\n",id[query(rt[p],1,N,v)]);
        }
    }
    return 0;
}

 

posted @ 2018-09-20 10:12  nimphy  阅读(305)  评论(0编辑  收藏  举报