好久没更新博客了,前段时间一直都在考试,都没时间些,现在终于有点闲了(cai guai)...
写了一道题,[HNOI2012]永无乡,其实是一道板子题,我发现我写了好多板子题...还是太菜了...
这道题有两个操作,合并和查询第k小,合并可以用到启发式合并,查询是平衡树,我这里写的是Splay+启发式合并.
启发式合,其实就是暴力合并,做法是将要合并的两个节点分别旋转到根,再把size小的拆掉,暴力插入到另一棵树里.这样做的复杂度是O(logn(拆散一棵树)*logn(插入另一棵树)),即O(logn^2).
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define inf (1<<30)
#define maxn (200010)
#define il inline
#define RG register
using namespace std;
il int gi(){ RG int x=0,q=1; RG char ch=getchar(); while( ( ch<'0' || ch>'9' ) && ch!='-' ) ch=getchar();
if( ch=='-' ) q=-1,ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x; }
int n,m,Q,Fa[maxn],w[maxn],que[maxn];
int size[maxn],ls[maxn],rs[maxn],fa[maxn];
il int find(int x){return Fa[x]==x?x:Fa[x]=find(Fa[x]);}
il void up(int x){ if(!x) return ; size[x]=size[ls[x]]+size[rs[x]]+1; }
il void rotate(int x){
RG int y=fa[x],z=fa[y];
bool v1=(x==ls[y]);
if(z) if(y==ls[z]) ls[z]=x; else rs[z]=x;
fa[x]=z,fa[y]=x;
if(v1) fa[rs[x]]=y,ls[y]=rs[x],rs[x]=y;
else fa[ls[x]]=y,rs[y]=ls[x],ls[x]=y;
up(y);
}
il void Splay(int x){
while(fa[x]){
RG int y=fa[x],z=fa[y];
if(z) if((x==ls[y])^(y==ls[z])) rotate(x);
else rotate(y);
rotate(x);
}
up(x);
}
il void Insert(RG int &x,RG int f,RG int y){
if(!x){ x=y,fa[x]=f,size[x]=1,Splay(x); return ; }
if(w[y]<=w[x]) Insert(ls[x],x,y);
else Insert(rs[x],x,y);
up(x);
}
il void Merge(int x,int y){
Splay(x),Splay(y);
if(size[x]>size[y]) swap(x,y);
RG int hd=0,tl=1; que[0]=y,que[1]=x;
while(hd<tl){
RG int cur=que[++hd];
if(ls[cur]) que[++tl]=ls[cur];
if(rs[cur]) que[++tl]=rs[cur];
ls[cur]=rs[cur]=0;
Insert(que[hd-1],0,cur);
}
}
il void Query(RG int x,RG int k){
if(k>size[x]){ printf("-1\n"); return ; }
if(k==size[ls[x]]+1){ printf("%d\n",x); return ; }
else if(k<size[ls[x]]+1){ Query(ls[x],k); return ; }
else{ Query(rs[x],k-size[ls[x]]-1); return ; }
}
il void init(){
n=gi(),m=gi(); int u,v;
for(RG int i=1;i<=n;i++) w[i]=gi(),Fa[i]=i,size[i]=1;
for(RG int i=1;i<=m;i++){
u=gi(),v=gi(); RG int f1=find(u),f2=find(v);
if(f1!=f2) Merge(u,v),Fa[f1]=f2;
}
}
il void work(){
Q=gi(); int x,y; char s[5];
for(RG int i=1;i<=Q;i++){
scanf("%s",s); x=gi(),y=gi();
if(s[0]=='B'){ RG int f1=find(x),f2=find(y);
if(f1!=f2) Merge(x,y),Fa[f1]=f1; }
else Splay(x),Query(x,y);
}
}
int main(){ init(); work(); return 0; }
对于这道题,还有一种方法,线段树合并,速度比Splay+启发式合并快,代码也比较短.
因为代码非常好理解,所以不再赘述,直接上代码.
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define inf (1<<30)
#define maxn (2000010)
#define ll long long
#define il inline
#define RG register
using namespace std;
il int gi(){ RG int x=0,q=1; RG char ch=getchar(); while( ( ch<'0' || ch>'9' ) && ch!='-' ) ch=getchar();
if( ch=='-' ) q=-1,ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x; }
int n,m,Q,cnt;
int w[maxn],pos[maxn],fa[maxn],rt[maxn];
int ls[maxn],rs[maxn],size[maxn];
int find(int x){ return x==fa[x]?x:fa[x]=find(fa[x]); }
void insert(RG int &x,RG int l,RG int r,RG int v){
if(!x) x=++cnt;
if(l==r){ size[x]=1; return; }
int mid=(l+r)>>1;
if(v<=mid) insert(ls[x],l,mid,v);
else insert(rs[x],mid+1,r,v);
size[x]=size[ls[x]]+size[rs[x]];
}
int query(RG int x,RG int l,RG int r,RG int k){
if(l==r) return l;
int mid=(l+r)>>1;
if(size[ls[x]]>=k) return query(ls[x],l,mid,k);
else return query(rs[x],mid+1,r,k-size[ls[x]]);
}
int Merge(RG int x,RG int y){
if(!x || !y) return x+y;
ls[x]=Merge(ls[x],ls[y]);
rs[x]=Merge(rs[x],rs[y]);
size[x]=size[ls[x]]+size[rs[x]];
return x;
}
il void init(){
n=gi(),m=gi(); int u,v,f1,f2;
for(RG int i=1;i<=n;i++) w[i]=gi();
for(RG int i=1;i<=n;i++) fa[i]=i;
for(RG int i=1;i<=m;i++){ u=gi(),v=gi();
f1=find(u),f2=find(v); fa[f1]=f2; }
for(RG int i=1;i<=n;i++){
insert(rt[find(i)],1,n,w[i]);
pos[w[i]]=i;
}
}
il void work(){
Q=gi(); int x,y,f1,f2; char s[5];
for(RG int i=1;i<=Q;i++){
scanf("%s",s);
x=gi(),y=gi();
if(s[0]=='Q'){
f1=find(x);
if(size[rt[f1]]<y){ printf("-1\n"); continue; }
printf("%d\n",pos[query(rt[f1],1,n,y)]);
}
else{ f1=find(x),f2=find(y);
if(f1!=f2) fa[f1]=f2,rt[f2]=Merge(rt[f1],rt[f2]); }
}
}
int main(){ init(); work(); return 0; }
最后祝大家切题愉快!!!