P1486 [NOI2004] 郁闷的出纳员 题解

文章介绍了Treap(FHQTreap)数据结构,强调其使用分裂和合并操作实现所有操作的时间复杂度为O(logn)。详细阐述了分裂和合并的算法,并提供了C++代码实现。随后,文章展示了如何利用Treap解决动态集合的插入、删除和查询问题,特别是针对元素值修改的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门

要想做这道题,我们要先了解无旋实现Treap(FHQ Treap)

FHQ Treap

特点:所有的操作都只用到了分裂与合并两个基本操作。且时间复杂度都为O(logn)。

分裂

将树拆为两颗子树L、R。

以键值为对象进行分裂。

x是分裂时的基准值。

L为根的子树上所有节点的键值都小于或等于x。

R为根的子树上的所有节点的键值都大于x。

分治的思想进行分裂。

步骤:

1.当前的节点键值<=x

       1.更新L为当前节点

       2.继续在右子树上进行分裂

2.当前节点的键值>x

       1.更新R为当前节点

       2.继续在左子树上进行分裂

代码如下:

struct node
{
	int ls,rs;//左、右孩子
	int val,pri;//val-键值 pri-优先级 
	int size;//以当前节点为根的子树节点总数 
}t[N];
void Update(int u)
{
	t[u].size=t[t[u].ls].size+t[t[u].rs].size+1;//t[u]的节点总数是左子树节点总数加右子树节点总数加自己 
}
void Split(int u,int x,int &L,int &R)//权值分裂 
{
	//u:当前treap树的根节点 
	//x:分裂的基准值 
	//L、R 分裂后的左右子树的根节点
	if(u==0)
    { 
		L=R=0;//到达叶子结点直接返回 
		return ;
	}
	
	if(t[u].val<=x)
    {
		L=u;//更新L为当前节点
		Split(t[u].rs,x,t[u].rs,R);
	}
    else
    {
		R=u;//更新R为当前节点
		Split(t[u].ls,x,L,t[u].ls);
	}
	Update(u);//更新 
}

合并

int Merge(int L,int R)//根据优先级合并、尽可能保持平衡  称为排序树 
{
	if(L==0||R==0) return L+R;//任意一棵子树为空的处理 将新树的根选为优先级更大的那个 
	if(t[L].pri > t[R].pri)
    {
		t[L].rs=Merge(t[L].rs,R);
		Update(L);//更新
		return L;
	}
    else
    {
		t[R].ls=Merge(L,t[R].ls);
		Update(R);//更新
		return R;
	}
}

完整代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,cnt=0,root;//cnt-节点存储到数组中的位置 root-树根 
struct node
{
	int ls,rs;//左、右孩子
	int val,pri;//val-键值 pri-优先级 
	int size;//以当前节点为根的子树节点总数 
}t[N];
int newNode(int x)
{
	t[++cnt].val=x;//读入权值 
	t[cnt].pri=rand();//随机优先级 
	t[cnt].ls=t[cnt].rs=0;//初始化左子树和右子树 
	t[cnt].size=1;//节点总数只有自己 
	return cnt;
}
void Update(int u)
{
	t[u].size=t[t[u].ls].size+t[t[u].rs].size+1;//t[u]的节点总数是左子树节点总数加右子树节点总数加自己 
}

void Split(int u,int x,int &L,int &R)//权值分裂 
{
	//u:当前treap树的根节点 
	//x:分裂的基准值
	//L、R 分裂后的左右子树的根节点 
	if(u==0)
	{
		L=R=0;//到达叶子节点直接返回 
		return ;
	}
	if(t[u].val<=x)
	{
		L=u;//更新L为当前节点 
		Split(t[u].rs,x,t[u].rs,R);
	}
	else
	{
		R=u;//更新R为当前节点 
		Split(t[u].ls,x,L,t[u].ls);
	}
	Update(u);//更新 
}
int Merge(int L,int R)//根据优先级合并、尽可能保持平衡 称为排序树 
{
	if(L==0 || R==0) return L+R;//任意一棵子树为空的处理 将新树的根选为优先级更大的那个 
	if(t[L].pri>t[R].pri)
	{
		t[L].rs=Merge(t[L].rs,R);
		Update(L);
		return L;
	}
	else
	{
		t[R].ls=Merge(L,t[R].ls);
		Update(R);
		return R;
	}
}
void Insert(int x)//将x插入Treap中
{
	int L,R;
	Split(root,x,L,R);//分裂成<=x和>x的两棵树 
	int aa=Merge(L,newNode(x));//合并L和新节点 
	root=Merge(aa,R);//合并左、右两棵树 
}
void Del(int x)
{
	int L,R,p;
	Split(root,x,L,R);
	Split(L,x-1,L,p);
	p=Merge(t[p].ls,t[p].rs);
	root=Merge(Merge(L,p),R);
}
int Rank(int x)//查询x数的排名
{
	int L,R;
	Split(root,x-1,L,R);
	int ans=t[L].size+1;//<x的节点个数加一 
	root=Merge(L,R);//重新合并回去
	return ans; 
}
int Kth(int u,int k)//查询排名为x的数
{
	if(k==t[t[u].ls].size+1) return u;//当前节点为第k个,直接返回
	else if(k<=t[t[u].ls].size) return Kth(t[u].ls,k);//排名小于左子树节点个数,在左子树上找 
	else return Kth(t[u].rs,k-t[t[u].ls].size-1);
}
int Precursor(int x)//前驱
{
	int L,R;
	Split(root,x-1,L,R);
	int ans=t[Kth(L,t[L].size)].val;
	root=Merge(L,R);
	return ans;
}
int Successor(int x)//后继
{
	int L,R;
	Split(root,x,L,R);
	int ans=t[Kth(R,1)].val;
	root=Merge(L,R);
	return ans;
}
int main()
{
	srand(time(NULL));
	return 0;
}

郁闷的出纳员

了解了FHQ Treap,我们就可以做这道题了

  1. I:插入数值
  2. A:所有元素加k
  3. S:所有元素减k
  4. F:查询第k大的数(从大到小)
  5. 删除元素、统计删除的元素个数

重点:所有元素的值的修改。新加入的员工,工资不受之前的修改影响。

代码如下,具体操作见注释:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int cnt=0,root;//cnt-节点存储到数组中的为止  root 树根
int n,m,add,ans; 
struct node{
	int ls,rs;//左右孩子 
	int val,pri;//键值、优先级
	int size;//以当前节点为根的子树节点总数 
}t[N];

int newNode(int x){
	t[++cnt].val=x;
	t[cnt].pri=rand();
	t[cnt].ls=t[cnt].rs=0;
	t[cnt].size=1;
	return cnt;
}

void Update(int u){
	t[u].size=t[t[u].ls].size+t[t[u].rs].size+1;
}

void Split(int u,int x,int &L,int &R){//权值分裂 
	//u:当前treap树的根节点 
	//x:分裂的基准值 
	//L、R 分裂后的左右子树的根节点
	if(u==0){ 
		L=R=0;//到达叶子结点直接返回 
		return ;
	}
	
	if(t[u].val<=x){
		L=u;//更新L为当前节点
		Split(t[u].rs,x,t[u].rs,R);
	}else{
		R=u;
		Split(t[u].ls,x,L,t[u].ls);
	}
	Update(u);//更新 
}

int Merge(int L,int R){//根据优先级合并、尽可能保持平衡  称为排序树 
	if(L==0||R==0) return L+R;//任意一棵子树为空的处理 将新树的根选为优先级更大的那个 
	if(t[L].pri > t[R].pri){
		t[L].rs=Merge(t[L].rs,R);
		Update(L);
		return L;
	}else{
		t[R].ls=Merge(L,t[R].ls);
		Update(R);
		return R;
	}
}

void Insert(int x){//将x插入Treap中
	int L,R;
	Split(root,x,L,R); //分裂成<=x 和 >x 的两棵树 
	int aa=Merge(L,newNode(x));//合并 L和新节点 
	root=Merge(aa,R);//合并左、右 两个树 
}

void Del(int x){
	//1.分裂成 L,R 
	//L:<=x  R:>x
	int L,R;
	Split(root,x,L,R);
	root=R;
	ans+=t[L].size;
}

int Kth(int k){//从大到小的第k个 
	int u=root;
	while(t[t[u].rs].size!=k-1){
		if(t[t[u].rs].size >= k) u=t[u].rs;
		else{
			k=k-t[t[u].rs].size-1;
			u=t[u].ls;
		}
	}
	return u;
}

int main()
{
	char c;
	int k;
	srand(time(NULL));
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>c>>k;
		if(c=='I'){
			if(k>=m) Insert(k-add);//插入工资档案
		}else if(c=='A') add+=k;//涨工资
		else if(c=='S'){
			add-=k;//减工资
			Del(m-1-add);//把离职的删掉
		}else{
			if(t[root].size<k) printf("-1\n");//如果没有这么多员工,返回-1
			else printf("%d\n",t[Kth(k)].val+add);//返回第k名的钱加涨的钱
		}
	}
	printf("%d\n",ans);
 	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值