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; }
It is your time to fight!