Luogu P3224 [HNOI2012]永无乡

题目大意

给出若干个点集,每个点带点权。你需要完成 q q q 个操作。

  1. 合并点 u u u v v v 所在的点集。
  2. 输出点 u u u 所在的点集中点权第 k k k 大的点的编号。

数据范围  1 ⩽ n ⩽ 100000 , 1 ⩽ q ⩽ 300000 1\leqslant n\leqslant100000,1\leqslant q\leqslant 300000 1n100000,1q300000

题解

求第k大,考虑权值线段树/Splay。
考虑到需要合并,可以考虑启发式合并。
可以发现,每个点最多被合并 log ⁡ n \log n logn 次,每次插入到另一颗权值线段树/Splay的时间为 Θ ( log ⁡ n ) \Theta(\log n) Θ(logn)。故总时间复杂度是有保证的,为 Θ ( n   log 2 n ) \Theta(n\,\text{log}^2n) Θ(nlog2n)

代码

S p l a y Splay Splay T i m e : 977 m s Time:977ms Time:977ms
这种方法没什么需要注意的,直接打就好。

#include
using namespace std;
struct node{//节点
    int size,data,n,id;
    node *fa,*ch[2];
    inline node(int ids,int d=0,node *f=NULL):data(d),size(1),n(1),fa(f),id(ids){
        ch[0]=ch[1]=NULL;
    }
    inline void up(){
        size=n;
        if(ch[0]) size+=ch[0]->size;
        if(ch[1]) size+=ch[1]->size;
    }
};
#define which(p) (p->fa->ch[1]==p)
struct Splay{//Splay主体
    node *root;
    inline Splay():root(NULL){
    }
    inline void newnode(node *&p,int id,int data,node *fa){
        p=new node(id,data,fa);
        *p=node(id,data,fa);
    }
    inline node *&down(node *&p,int x){
        if(x==p->data) return p;
        return p->ch[x>p->data];
    }
    inline void rotate(node *p){
        int t=which(p);
        node *f=p->fa;
        if((f->ch[t]=p->ch[t^1]))
            p->ch[t^1]->fa=f;
        if((p->fa=f->fa))
            p->fa->ch[which(f)]=p;
        f->fa=p;p->ch[t^1]=f;
        f->up();p->up();
        if(p->fa==NULL)
            root=p;
    }
    inline void splay(node *p,node *Fa){
        for(;p->fa!=Fa;rotate(p))
            if(p->fa->fa!=Fa)
                rotate(which(p)==which(p->fa)?p->fa:p);
    }
    inline void insert(node *p,int id,int val){
        if(root==NULL){
            newnode(root,id,val,NULL);
            return ;
        } node *fa=p->fa;
        while(p!=NULL){
            node *t=down(p,val);
            if(t==p)
                break;
            fa=p;p=t;
        } if(p==NULL){
            newnode(p,id,val,fa);
            down(fa,val)=p;
        }else
            ++p->n;
        splay(p,NULL);
    } inline void insert(int id,int x){insert(root,id,x);}
    inline node *kth(node *p,int k){
        while(p){
            int d=p->ch[0]?p->ch[0]->size:0;
            if(k>=d+1&&k<=d+p->n){
                splay(p,NULL);
                return p;
            }
            if(k<d+1) p=p->ch[0];
            else k-=d+p->n,p=p->ch[1];
        }
        return NULL;
    } inline node *kth(int k){return kth(root,k);}
    friend void travel_insert_del(node *t,Splay *y){
		y->insert(t->id,t->data);
		if(t->ch[0]!=NULL) travel_insert_del(t->ch[0],y);
		if(t->ch[1]!=NULL) travel_insert_del(t->ch[1],y);
		delete t;//保证空间复杂度为 $\Theta(n)$
	}
    void operator+=(Splay &_x){//启发式合并
    	Splay *x=this,*y=&_x;
    	travel_insert_del(y->root,x);
    	y->root=NULL;
	}
}splay[100010];
int fa[100010];//并查集维护根
int findfa(int x){
	if(fa[x]==x) return x;
	return fa[x]=findfa(fa[x]);
}
void link(int u,int v){
	int fx=findfa(u),fy=findfa(v);
	if(fx==fy) return;
	if(splay[fx].root->size<splay[fy].root->size){
		fa[fx]=fy;
		splay[fy]+=splay[fx];
	}else{
		fa[fy]=fx;
		splay[fx]+=splay[fy];
	}
}
int n,m;
char str[110];
int main(void){
	scanf("%d%d",&n,&m);
	for(int i=1,x;i<=n;i++){
		fa[i]=i;scanf("%d",&x);
		splay[i].insert(i,x);
	}
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);
		link(u,v);
	}
	scanf("%d",&m);
	for(int i=1,u,v;i<=m;i++){
		scanf("%s%d%d",str,&u,&v);
		if(str[0]=='Q'){
			node *t=splay[findfa(u)].kth(v);
			if(t) printf("%d\n",t->id);
			else printf("-1\n");
		}else link(u,v);
	}
	return 0;
}

权值线段数, T i m e : 1547 m s Time:1547ms Time:1547ms
考虑到要保证时间和空间复杂度,这里最好(必须)动态开点。

#include
using namespace std;
struct tree{
    int l,r,d,cur;
    tree *left,*right;
    tree(int tl=0,int tr=0):l(tl),r(tr),left(NULL),right(NULL),d(0),cur(-1){
    } void set_up(int tl,int tr){*this=tree(tl,tr);}
    void insert(int x,int tcur){//加入
        if(l==r) return (void)(d=1,cur=tcur);
        int mid=(l+r)>>1;
        if(x<=mid){
        	if(!left) left=new tree(l,mid);
			left->insert(x,tcur);
        }else{
        	if(!right) right=new tree(mid+1,r);
			right->insert(x,tcur);
		}d=0;
		if(left) d+=left->d;
		if(right) d+=right->d;
    }
    int kth(int k){//排名
    	if(k>d||k<1) return -1;
    	if(l==r) return cur;
    	if(left&&left->d>=k) return left->kth(k);
    	return right->kth(k-(left?left->d:0));
    } friend void merge(tree*,tree*);
}t[100010];
void merge(tree *a,tree *b){//合并
	a->d+=b->d,b->d=b->cur=0;
	a->cur=max(a->cur,b->cur);
	if(a->left&&b->left){
		merge(a->left,b->left);
		delete b->left;
		b->left=NULL;
	}else if(b->left)
		a->left=b->left;//直接送过去就好
	if(a->right&&b->right){
		merge(a->right,b->right);
		delete b->right;
		b->right=NULL;
	}else if(b->right)
		a->right=b->right;//直接送过去就好
}
int fa[100010];
int findfa(int x){
	if(fa[x]==x) return x;
	return fa[x]=findfa(fa[x]);
}
void link(int u,int v){//连接
	int fx=findfa(u),fy=findfa(v);
	if(fx!=fy){
		if(t[fx].d>t[fy].d){
			fa[fy]=fx;
			merge(t+fx,t+fy);
		}else{
			fa[fx]=fy;
			merge(t+fy,t+fx);
		}
	}
}
int n,m;
char str[110];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,x;i<=n;i++){
		scanf("%d",&x);
		t[i].set_up(1,n);
		t[fa[i]=i].insert(x,i);
	}
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);
		link(u,v);
	}
	scanf("%d",&m);
	for(int i=1,u,v;i<=m;i++){
		scanf("%s%d%d",str,&u,&v);
		if(str[0]=='Q'){
			printf("%d\n",t[findfa(u)].kth(v));
		}else link(u,v);
	}
	return 0;
}

你可能感兴趣的:(Splay,启发式合并)