BZOJ 3224 普通平衡树 裸treap模板题

题意: (裸题还用我说么)

方法: (裸题还用我说么)

解析: 第一次写treap还是费点劲的尤其在理解的时候

首先,定义如下

struct data
{
    int l , r , v , rnd , size , w ;
};
data tr[100001] ;
int n , ans , size , root ;
void update(int k)
{
    tr[k].size = tr[tr[k].l].size + tr[tr[k].r].size + tr[k].w ;
}
void lturn(int &k)
{
    int t = tr[k].r ;
    tr[k].r = tr[t].l ;
    tr[t].l = k ;
    tr[t].size = tr[k].size ;
    update(k) ;
    k = t ;
}
void rturn(int &k)
{
    int t = tr[k].l ;
    tr[k].l = tr[t].r ;
    tr[t].r = k ;
    tr[t].size = tr[k].size ;
    update(k) ;
    k = t ;
}

左旋右旋只要画两个图,按照图来写就行了,记住6行代码(6句)就不会错。

其次: 插入代码

void insert(int &k , int x)
{
    if(k == 0)
    {
        size ++ ;
        k = size ;
        tr[k].size = tr[k].w = 1 ;
        tr[k].v = x ;
        tr[k].rnd = rand() ;
        return ;
    }
    tr[k].size ++ ;
    if(tr[k].v == x) tr[k].w ++ ;
    else if(x > tr[k].v)
    {
    	insert(tr[k].r , x) ;
    	if(tr[tr[k].r].rnd < tr[k].rnd) lturn(k) ;
    }else 
    {
    	insert(tr[k].l , x) ;
    	if(tr[tr[k].l].rnd < tr[k].rnd) rturn(k) ;
    }
}
1.先讨论没有节点的情况

2.再讨论所插节点恰好在k上

3.再讨论所插节点的值与k处的值的大小关系,若大于则插到右子树,反之插到左子树。同时要维护小根堆的特性。


删除函数

void del(int &k , int x)
{
	if(k == 0) return ;
	if(tr[k].v == x)
	{
		if(tr[k].w > 1)
		{
			tr[k].w -- , tr[k].size -- ;
			return ;
		}
		if(tr[k].l * tr[k].r == 0) k = tr[k].l + tr[k].r ;
		else if(tr[tr[k].l].rnd < tr[tr[k].r].rnd)
		{
			rturn(k) ;
			del(k , x) ;
		}else
		{
			lturn(k) ;
			del(k , x) ;
		}
	}else if(x > tr[k].v)
	{
		tr[k].size -- ;
		del(tr[k].r , x) ;
	}else
	{
		tr[k].size -- ;
		del(tr[k].l , x) ;
	}
}
1.讨论没有节点不删除

2.如果恰好在k处

1.k处的数多于1个则直接删除

2.k处只有一个子节点,则该节点替换k

3.如果左边rnd小于右边rnd,右旋,把这个根节点旋到叶节点处删掉

4.与(3)相反则左旋到叶节点删除

3.如果比k处的值大则k的size--,再分到右子树删除。

4.如果比k处的值小则k的size--,再分到左子树删除。


查询x的排名

int query_rank(int k , int x)
{
	if(k == 0) return 0 ;
	if(tr[k].v == x) return tr[tr[k].l].size + 1 ;
	else if(x > tr[k].v)
	{
		return tr[tr[k].l].size + tr[k].w + query_rank(tr[k].r , x) ;
	}else return query_rank(tr[k].l , x) ;
}
1.讨论没有节点的情况

2.如果恰好就是在k处则直接返回左子树的节点数加1

3.如果比k处的值大则返回左子树的节点数加k处点数加对右子树的递归询问

4.如果比k处的值小则返回左子树的递归询问


查询排名为x的数

int query_num(int k , int x)
{
	if(k == 0) return 0 ;
	if(x <= tr[tr[k].l].size)
	{
		return query_num(tr[k].l , x) ;
	}else if(x > tr[tr[k].l].size + tr[k].w)
	{
		return query_num(tr[k].r , x - tr[tr[k].l].size - tr[k].w) ;
	}else
	{
		return tr[k].v ;
	}
}
1.讨论没有节点的情况

2.如果x小于等于左子树的节点数返回左子树的递归询问

3.如果x比左子树的节点数加k处节点数还大返回对右子树的 x-左子树节点数-k处节点数 的递归询问

4.除了2.3.的情况则一定是k处的数,返回k处的值


求前驱

void query_pro(int k , int x)
{
	if(k == 0) return ;
	if(tr[k].v < x)
	{
		ans = k ;
		query_pro(tr[k].r , x) ;
	}else query_pro(tr[k].l , x) ;
}
1.讨论没有节点的情况

2.如果x比k处的值大则用ans记录k,再二分到右子树

3.与2相反则二分到左子树


求后继

void query_sub(int k , int x)
{
	if(k == 0) return ;
	if(tr[k].v > x)
	{
		ans = k ;
		query_sub(tr[k].l , x) ;
	}else query_sub(tr[k].r , x) ;
}
1.讨论没有节点的情况

2.如果x比k处的值小则用ans记录k再二分到左子树

3.与2相反则二分到右子树


主函数(这货完全就是来凑字数的)

int main()
{
    scanf("%d" , &n) ;
    int opt , x ;
    for(int i = 1 ; i <= n ; i++)
    {
    	scanf("%d%d" , &opt , &x) ;
    	switch(opt)
    	{
	    	case 1 :insert(root , x) ;break ;
	    	case 2 :del(root , x) ;break ;
	    	case 3 :printf("%d\n" , query_rank(root , x)) ;break ;
	    	case 4 :printf("%d\n" , query_num(root , x)) ;break ;
	    	case 5 :ans = 0;query_pro(root , x);printf("%d\n" , tr[ans].v);break ;
	    	case 6 :ans = 0;query_sub(root , x);printf("%d\n" , tr[ans].v);break ;
	    }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值