【POJ 2985】 The k-th Largest Group Treap+并查集

Treap:佛系平衡树,爱转不转,靠随机,靠随缘,只有单旋转。

比普通的二叉树多一个priority值,保存结点的权重用随机数生成。

如果一个节点的儿子比他的父亲优先级大【或小】,就执行一次单选转。

期望平均O(nlgn) 可以说是很随缘了。


删除稍微复杂一点:

1、叶子节点,直接消失。

2、没有左儿子/右儿子,把该节点反复执行相应旋转,转到情况1,直接删除。

3、朝优先级低的那方向转。


模板

const int maxn = 220000;
struct Treap{
	int root,treapCnt,key[maxn],priority[maxn],
	childs[maxn][2],cnt[maxn],size[maxn];
	
	Treap(){
		root = 0;
		treapCnt = 1;
		priority[0] = INT_MAX;
		size[0] = 0;
	} 
	
	inline void update(int x){
		size[x] = size[ childs[x][0] ] + size[ childs[x][1] ] + cnt[x]; 
	} 
	
	inline void rotate(int& x, int t){
		int y = childs[x][t];
		childs[x][t] = childs[y][!t];
		childs[y][!t] = x;
		update(x);
		update(y);
		x = y;
	}
	
	void _insert(int& x, int k)
	{
		if(x){
			if(key[x] == k)
				cnt[x]++;
			else
			{
				int t = key[x] < k;
				_insert(childs[x][t],k);
				if(priority[ childs[x][t] ] < priority[x])
					rotate(x,t);	
			}
		}
		else
		{
			x = treapCnt++;
			key[x] = k;
			if(k != 1) 
				cnt[x] = 1;
			else
				cntx
			childs[x][0] = childs[x][1] = 0;
			priority[x] = rand();
		}
		update(x); 
	}
	
	void _erase(int& x, int k)
	{
		if(key[x] == k)
		{
			if(cnt[x] > 1)
				cnt[x]--;
			else
			{
				if(childs[x][0] == 0 && childs[x][1] == 0)
				{
					x = 0;
					return;
				}
				int t = priority[ childs[x][0] ] > priority[ childs[x][1] ];
				rotate(x,t);
				_erase(x,k);
			}
		} else {
			_erase(childs[x][key[x] < k] , k);
		}
		update(x);
	}
	
	
	//第k大 可自行改
	int get_Kth(int& x,int k){
		if(k <= size[ childs[x][1] ])
			return get_Kth(childs[x][1],k);
		
		k -= size[childs[x][1]] + cnt[x];
		
		if(k <= 0)
			return key[x];
		else
			return get_Kth(childs[x][0],k);
	}
	
	void insert(int k){
		_insert(root,k);
	}
	void erase(int k){
		_erase(root,k);
	}
	int getKth(int k){
		return get_Kth(root,k);
	} 	
}; 


然后讨论 The k-th Largest Group 这道题

题意:有一群猫,编号1~n。

有两种操作: 1、把编号为i的猫所在的集合和编号j的猫所在集合里的所有猫全部并在一起。

                      2、寻味集合大小排名为k的猫群有多少只猫。

分析:

        1、很明显这道题必然涉及并查集操作,然而还不够,仍要维护并查集上的一个值,保存当前集合有多少只猫。维护最高祖先就可以了。

        2、treap的key保存集合大小,size保存集合数量。因此在合并的时候,进行两次删除操作,值为原来两个集合大小。然后插入一个值二者大小之和的元素就可以了。

【另:节点为1其实可以不用删,因为数据合理的情况下求第k大是用不到key = 1的节点。另外开局插入时,直接令size[值为1的节点] = N 可以省去麻烦】

#include <cstdio>
#include <string>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
using namespace std;
int N = 0 , M = 0;
const int maxn = 220000;
struct Treap{
	int root,treapCnt,key[maxn],priority[maxn],
	childs[maxn][2],cnt[maxn],size[maxn];
	
	Treap(){
		root = 0;
		treapCnt = 1;
		priority[0] = INT_MAX;
		size[0] = 0;
	} 
	
	inline void update(int x){
		size[x] = size[ childs[x][0] ] + size[ childs[x][1] ] + cnt[x]; 
	} 
	
	inline void rotate(int& x, int t){
		int y = childs[x][t];
		childs[x][t] = childs[y][!t];
		childs[y][!t] = x;
		update(x);
		update(y);
		x = y;
	}
	
	void _insert(int& x, int k)
	{
		if(x){
			if(key[x] == k)
				cnt[x]++;
			else
			{
				int t = key[x] < k;
				_insert(childs[x][t],k);
				if(priority[ childs[x][t] ] < priority[x])
					rotate(x,t);	
			}
		}
		else
		{
			x = treapCnt++;
			key[x] = k;
			if(k != 1) 
				cnt[x] = 1;
			else
				cnt[x] = n;
			childs[x][0] = childs[x][1] = 0;
			priority[x] = rand();
		}
		update(x); 
	}
	
	void _erase(int& x, int k)
	{
		if(key[x] == k)
		{
			if(cnt[x] > 1)
				cnt[x]--;
			else
			{
				if(childs[x][0] == 0 && childs[x][1] == 0)
				{
					x = 0;
					return;
				}
				int t = priority[ childs[x][0] ] > priority[ childs[x][1] ];
				rotate(x,t);
				_erase(x,k);
			}
		} else {
			_erase(childs[x][key[x] < k] , k);
		}
		update(x);
	}
	
	
	//第k大 
	int get_Kth(int& x,int k){
		if(k <= size[ childs[x][1] ])
			return get_Kth(childs[x][1],k);
		
		k -= size[childs[x][1]] + cnt[x];
		
		if(k <= 0)
			return key[x];
		else
			return get_Kth(childs[x][0],k);
	}
	
	void insert(int k){
		_insert(root,k);
	}
	void erase(int k){
		_erase(root,k);
	}
	int getKth(int k){
		return get_Kth(root,k);
	} 	
}; 

Treap T;
int fa[maxn];
int size[maxn];

int find(int t){
	int x = t;
	while(x != fa[x])
		x = fa[x];
	return fa[t] = x;
}

void Union(int x , int y){
	int fx = find(x);
	int fy = find(y);
	if(fx != fy)
	{
		if(size[fx] != 1) T.erase(size[fx]);
		if(size[fy] != 1) T.erase(size[fy]);
		int k = size[fx] + size[fy];
		T.insert(k);
		fa[fx] = fy;
		size[fy] = k;
	}
}

inline void init_set(){
	for (int i = 0 ; i < maxn ; ++i)
	{
		size[i] = 1;
		fa[i] = i;
	}
}

int main(){
	
	init_set();
	scanf("%d%d",&N,&M);

	int x = 0 , y = 0 , z = 0;
	T.insert(1);
	for (int i = 0 ; i < M ; ++i) {
		scanf("%d",&x);
		if(x == 1)
		{
			scanf("%d",&y);
			printf("%d\n",T.getKth(y));
		}
		else
		{
			scanf("%d%d",&y,&z);
			Union(y,z);
		}
	}
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值