主席树浅解(不带修改)

萌新刚学OI,之前一直听人讲起莫队主席树的高端大气上档次的算法,但一直没去学,直到最近才刚刚了解这个黑科技(好吧,就是今天上午)。理解了一上午,写了好久板子后,我才大致搞懂,就准备写篇博客梳理梳理自己的思路。(理解颇有简陋之处还望谅解)

1.主席树的理解

主席树又叫做可持久化线段树,因为它具有极为强大的功能:他不仅能像线段树一样可以维护、修改和、最值等,它还可以支持访问历史操作,比如:要求维护一颗线段树,而且还要能在完成某次修改后支持对前某次修改的线段树的查询。看到这,想必会有人说:不就是查询历史操作吗,我也行,建n颗线段树就行了。但,大多数时候这样暴力建n棵树可能(显然)会mle,但主席树优秀的地方就在于它可以极大程度上优化空间复杂度,使得原本n^2 的复杂度锐减为nlogn。

2.主席树的实质

主席树之所以可以将空间降到这种程度,是因为它将开辟多棵线段树相同的部分共用了,即每棵线段树共用相同的结点,而不同的结点会自己单独开辟。这时,我们会发现,如果每次修改只修改一个结点的信息,那么这一次修改操作只重新开辟了从根节点到它的一条链,其余结点与前一颗线段树共用,达到节省空间的目的。那么如何实现上面的操作呢?代码如下:

void update(ll& id , ll l , ll r , ll last , ll pos , ll val)
{
	id = ++tot;
	//动态开点,所以主席树上的点是离散的,不满足子结点的id为父结点的2倍或2倍+1
	ls[id] = ls[last];
	rs[id] = rs[last];
	//当前树与前一棵树共用左右儿子结点
	更新(如sum[id] += val,maxn[id] = max(maxn[id],val)等)
	if (l == r)
	{
		return;
	}
	ll mid = (l + r) >> 1;
	if (pos <= mid)
	{
		update(ls[id] , l , mid , ls[last] , pos , val);
	}
	else
	{
		update(rs[id] , mid + 1 , r , rs[last] , pos , val);
	}
	//要修改的点单独开辟
}

实现了修改,该怎样解决查询呢,很简单,只要以需要查询的某个根节点为根,不断查询满足条件的左右儿子就行了。代码如下:

ll query(ll s , ll l , ll r , ll k)
{
	if (cnt[s] <= k)//当前结点满足条件
	{
		return sum[s];
	}
	else if (l == r)
	{
		return sum[s] / cnt[s] * k;
	}
	ll mid = (l + r) >> 1;
	if (cnt[ls[s]] >= k)//左结点满足条件
	{
		return query(ls[s] , l , mid , k);
	}
	else//左右结点一起查询
	{
		return query(ls[s] , l , mid , cnt[ls[s]]) + query(rs[s] , mid + 1 , r , k - cnt[ls[s]]);
	}
}

以上代码为CQOI2015任务查询系统中的部分代码,不具有一般性,但查询的大致思想一致。传送门:https://blog.csdn.net/weixin_43790474/article/details/84453817
第一篇博客,本来后面有配合一道板子讲的,不过写的太烂了,就删了,写的不好,多多包涵。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值