永无乡 HNOI 2012

题面:https://www.luogu.com.cn/problem/P3224
给你n个点,每次合并两个点所在集合,动态询问每个点所在集合的第k大值

看到动态第k大值,便很容易想到平衡树
因为要合并集合,就不能用普通treap,可以用splay
但这里要讲的是FHQtreap
先建n棵FHQtreap,每次合并两棵树,正确性毋庸置疑
如何实现? 时间复杂度?

启发式合并

登场

每次将节点数小的树拆成一个一个的节点依次合并到节点数大的树上,时间复杂度为
O ( n l o g 2 n ) O(n log^2 n) O(nlog2n)

证:对于任意一个节点 x x x,每一次合并,它所在树的节点树都要乘2,所以它最多被合并 l o g 2 n log_2n log2n
(eg:第一次合并,x所在树结点个数为2,第二次合并结点个数为4,第三次合并为8…)
又因为有n个这样的结点,每次合并复杂度为 l o g 2 n log_2n log2n,所以:最坏复杂度 O ( n l o g 2 n ) O(n log^2 n) O(nlog2n)

C o d e Code Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5,Mod=1<<30;
struct FHQ{
	int lc,rc,size,val,rank,fa;
	FHQ(){lc=rc=size=fa=0;}
}tree[MAXN+10];
inline int read();
inline void update(int);
void Insert(int,int);
void merge(int&,int,int);
void dfs(int&,int,int);
void spilt(int,int&,int&,int);
int Query(int,int);

int main(){
	freopen ("std.in","r",stdin);
	freopen ("std.out","w",stdout);
	srand((unsigned)time(NULL));
	int n=read(),m=read();
	for (register int i=1;i<=n;++i){
		tree[i].rank=rand()%Mod;
		tree[i].val=read();
		tree[i].size=1;
	}	
	for (register int i=1;i<=m;++i)	Insert(read(),read());
	m=read();
	for (register int i=1;i<=m;++i){
		char c=getchar();
		while (c!='B' && c!='Q')	c=getchar();
		int x=read(),y=read();
		if (c=='B')	Insert(x,y);
		else	printf("%d\n",Query(x,y));
	}
	return 0;
}

inline int read(){
	int x=0;
	char c=getchar();
	while (!isdigit(c))c=getchar();
	while (isdigit(c))x=(x<<3)+(x<<1)+(c&15),c=getchar();
	return x;
}

inline void update(int rt){
	tree[rt].size=tree[tree[rt].lc].size+tree[tree[rt].rc].size+1;
	tree[tree[rt].lc].fa=tree[tree[rt].rc].fa=rt;
}

void spilt(int rt,int &rx,int &ry,int val){
	if (!rt){
		rx=ry=0;
		return;
	}
	if (tree[rt].val<=val)	spilt(tree[rt].rc,tree[rx=rt].rc,ry,val);
	else    spilt(tree[rt].lc,rx,tree[ry=rt].lc,val);
	update(rt);
}

void merge(int &rt,int rx,int ry){
	if (!rx || !ry){
		rt=rx+ry;
		return;
	}
	if (tree[rx].rank<tree[ry].rank)	merge(tree[rt=rx].rc,tree[rx].rc,ry);
	else    merge(tree[rt=ry].lc,rx,tree[ry].lc);
	update(rt);
}

void Insert(int rt,int z){
	while (tree[rt].fa)	rt=tree[rt].fa;
	while (tree[z].fa)	z=tree[z].fa;
	if (rt==z)	return;
	if (tree[rt].size<tree[z].size)	swap(rt,z);
	dfs(rt,z,0);
}

void dfs(int &rt,int x,int fa){
	if (!x)	return;
	dfs(rt,tree[x].lc,x);
	dfs(rt,tree[x].rc,x);
	tree[x].lc=tree[x].rc=tree[x].fa=0;	tree[x].size=1;
	int rx=0,ry=0;
	spilt(rt,rx,ry,tree[x].val);
	merge(rx,rx,x);
	merge(rt,rx,ry);
}

int Query(int rt,int k){
	while (tree[rt].fa)	rt=tree[rt].fa;
	if (k>tree[rt].size)	return -1;
	while (k!=tree[tree[rt].lc].size+1){
		if (k<=tree[tree[rt].lc].size)	rt=tree[rt].lc;
		else	k-=tree[tree[rt].lc].size+1,rt=tree[rt].rc;
	}
	return rt;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值