P2464 [SDOI2008]郁闷的小J 平衡树Treap

题面链接
打完平衡树模板后第一道实战题

思路:平衡树套平衡树

最开始

铅笔在纸上游走,一点思路也没有……

后来回归原始,想着用数组(编号桶排序)+前缀和(位置),结果发现对于100%的数据,所有出现的书的编码为不大于2147483647的正数。

发现要炸,那么有没有一种方法,使得编号储存和查找不费空间,又不费时间;使位置储存和查找不费空间,又不费时间呢?

想到刚打完模板题的平衡树:由于平衡树具有有序性,而且可以保持树的期望树高为 O ( log ⁡ n ) O(\log n) O(logn),每次操作期望时间复杂度为 Θ ( log ⁡ n ) \Theta(\log n) Θ(logn)

我们可以利用此特性提高我们查书的速度

构造

既然查编号要查得快,查位置也要查得快,不如就给这两者都设一棵平衡树:

对于每一个特定编号,都设一棵平衡树记录这一编号的书所在的位置,
再设一棵平衡树将所有特定编号串起来以便查询。

平衡树套平衡树

查询

那么对于查询操作,先在编号平衡树里找到编号,再在这个编号对应的位置平衡树里
找到范围内最左边的书(左边界的后继)和最右边(右边界的前驱)的书
获取这两本书在位置平衡树里的排名,二者相减+1就能得到这一区间内书的数量
时间复杂度 l o g n × 4 l o g n logn\times 4logn logn×4logn,省略常数后得 Θ ( log ⁡ n 2 ) Θ(\log n^2) Θ(logn2)

修改

对于修改操作,在编号平衡树里找到被取代的书编号,再在这个编号对应的位置平衡树里删除书本,如果本编号的书删没了,顺便把编号也删了

再把插入的书插入到平衡树里,时间复杂度 2 × log ⁡ n × log ⁡ n 2\times \log n \times \log n 2×logn×logn Θ ( log ⁡ n 2 ) Θ(\log n^2) Θ(logn2)

一次AC哦!

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<ctime>
#include<assert.h>

using namespace std;
int m,n,x,y,que,lt,rt,bo,ans,tmp[600005];
char ch;
struct posnode{
    
    //记录某一编码的所有书的位置的平衡树 
	posnode *son[2];
	int pos;int key;
	int tsize;
	void getsize()//获取树的大小 
	{
    
    
		this->tsize=1;
		if(this->son[0]) this->tsize+=this->son[0]->tsize;
		if(this->son[1]) this->tsize+=this->son[1]->tsize;
	}
}mem1[6666666],*now1 = mem1;

struct node{
    
    
	node *son[2];//son[0]:left son;son[1]:right son;
	int val;int key;
	posnode *rec;//记录某一编码的所有书的位置的平衡树 
}mem[6666666],*now = mem,*root;

posnode *newposi(int where)
{
    
    
	posnode *q = now1++;
	q->pos = where;
	q->key = rand();
	q->son[0] = q->son[1] = NULL;
	q->tsize = 1;
	return q;
}

node *newnode(int num)
{
    
    
	node *p = now++;
	p->val = num;
	p->key = rand();
	p->rec = NULL;
	return p;
}

void Rotatep(posnode *&p,int k)//k=0:左转 k=1:右转 
{
    
    
	posnode *tmp = p->son[k^1];
	p->son[k^1] = tmp->son[k];
	tmp->son[k] = p;
	p->getsize();//记得更新!!!! 
	p = tmp;
	p->getsize();//记得更新!!! 
	return ;
}

void Rotate(node *&p,int k)//k=0:左转 k=1:右转 
{
    
    
	node *tmp = p->son[(k^1)];
	p->son[(k^1)] = tmp->son[k];
	tmp->son[k] = p;
	p = tmp;
	return ;
}

void Insertrec(posnode *&q,int where)//维护记录本编码的书的位置的平衡树 
{
    
    
//	cout<<"ok2"<<endl;
	if(q == NULL)//找到位置就放 
	{
    
    
//		cout<<"ok2"<<endl;
		q = newposi(where);		
		return ;
	}
//	cout<<"what's up"<<endl;
	int k = where > q->pos;//书的位置小于当前取0,大于当前取1 
	Insertrec(q->son[k] , where);
	q->getsize();//更新 
	if(q->key > q->son[k]->key)//破坏堆序就旋转 
		Rotatep(q,(k^1));
	return ;
}

void Insertx(node *&p,int num,int where)//插入新书 
{
    
    
//	cout<<"ok"<<endl;
	if(p == NULL)//找到位置就放 
	{
    
    
		p = newnode(num);
//		cout<<"ok"<<endl;
		Insertrec(p->rec,where);//记录  
		return ;
	}
	if(p->val == num)//编码相同就记录 
	{
    
    
		Insertrec(p->rec,where);		
		return ;
	}
	int k = num > p->val;//编码小于当前取0,大于当前取1 
	Insertx(p->son[k] , num, where);
	if(p->key > p->son[k]->key)//破坏堆序就旋转 
		Rotate(p,(k^1));
	return ;
}


void Deleterec(posnode *&q,int where)
{
    
    
	if(q == NULL) return ;//空即返回 
	if(q->pos == where)//找到书的位置 
	{
    
    
		if(q->son[1]&&q->son[0])//有两个儿子 
		{
    
    
			int k = q->son[0]->key < q->son[1]->key;//如果左儿子修正值>右儿子修正值就右旋,否则左旋 
			Rotatep(q ,k);
			Deleterec(q->son[k],where);
			q->getsize();//更新树的大小 
		}
		else
		{
    
    
			if(q->son[1])//只有右儿子 
				q = q->son[1];
			else
			{
    
    
				if(q->son[0])//只有左儿子 
					q = q->son[0];
				else//没有儿子直接删除 
					q = NULL;
			}
		}
		return ;
	}
	else
	{
    
    
		int k = where > q->pos;
		Deleterec(q->son[k],where);//继续找书的位置 
		q->getsize();//更新书的数量 
	}
}

void Deletex(node *&p,int num,int where)
{
    
    
	if(p == NULL) return ;//空即返回 
	if(p->val == num)
	{
    
    
		Deleterec(p->rec,where);//将书本 9从书架上取下;
		if(p->rec)
			return ;
		else
		{
    
    
			if(p->son[1]&&p->son[0])//有两个儿子 
			{
    
    
				int k = p->son[0]->key < p->son[1]->key;//如果左儿子修正值>右儿子修正值就右旋,否则左旋 
				Rotate(p ,(k^1));
				Deletex(p->son[(k^1)],num,where);
			}
			else
			{
    
    
				if(p->son[1])
					p = p->son[1];
				else
				{
    
    
					if(p->son[0])
						p = p->son[0];
					else
						p = NULL;
				}
			}
			return ;
		}
			
	}
	else
	{
    
    
		int k = num > p->val;
		Deletex(p->son[k],num, where);//继续找书的编号 
	}
}


int Getsuc(posnode *&q,int num)//求后继 
{
    
    
	if(q == NULL) return -1;//没有找到返回-1 
	if(num > q->pos)//找比num大的数 
		return Getsuc(q->son[1],num);
	else
	{
    
     //最小的比num大的数 
		int tmp = Getsuc(q->son[0],num);
		if(tmp != -1) return tmp;
		else return q->pos;
	}
}

int Getpre(posnode *&q,int num)//求前驱 
{
    
    
	if(q == NULL) return -1;
	if(num < q->pos)//找比num小的数 
		return Getpre(q->son[0],num);
	else
	{
    
     //最大比num小的数 
		int tmp = Getpre(q->son[1],num);
		if(tmp != -1) return tmp;
		else return q->pos;
	}
}

int Getrank(posnode *&q,int num)//求排名 
{
    
    
	if (q == NULL) return 0;
	if (q->pos >= num) return Getrank(q->son[0], num);//
	int lsize = 0;
	if( q->son[0] ) lsize = q->son[0]->tsize;
	return lsize + Getrank(q->son[1], num) + 1;
}

void Getans(node *&p,int num,int l,int r)//查询操作 
{
    
    
	if(p == NULL) return ;
	if(num == p->val)
	{
    
    
		ans = 0;
		int rightbook = Getpre(p->rec,r);//找到范围内最右边的书 
		if(rightbook == -1) return ;
		int leftbook = Getsuc(p->rec,l); //找到范围内最左边的书 
		if(leftbook == -1) return ;
		int lrank = Getrank(p->rec,leftbook);//求出左边的书在本编码所有书内的排名 
		int rrank = Getrank(p->rec,rightbook);//求出右边的书在本编码所有书内的排名 
		ans = rrank - lrank + 1;//范围内书的数量 
		return ;
	}
	else
	{
    
    
		int k = num > p->val;
		Getans(p->son[k],num,l,r);
		return ;
	}
}

int main()
{
    
    
	root = NULL;
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i ++)//插入原始书的序列
	{
    
    
		scanf("%d", &tmp[i]);
		Insertx(root,tmp[i],i);
	}
	for(int i = 1;i <= m;i ++)
	{
    
    
		cin>>ch;
		if(ch == 'Q')//查询操作
		{
    
    
			scanf("%d%d%d",&lt,&rt,&bo);
			ans = 0;
			Getans(root,bo,lt,rt);
			printf("%d\n",ans);
		}
		else
		{
    
    
			scanf("%d%d",&x,&y);//修改=插入+删除
			Deletex(root,tmp[x],x);
			Insertx(root,y,x);
			tmp[x]=y;
		}
	 } 
	return 0;
}

猜你喜欢

转载自blog.csdn.net/bell041030/article/details/107642507