主席、树、主席树!

        隔了好久没来,又开学了,一切恍如昨日……

        主席树,听起来真的是高大上(毕竟是主席啊)。那么,什么是主席树呢?说白了,就是很多棵线段树的整合(貌似可以树状数组),如果学习过Link Cut Tree就会知道,是类似的。Link Cut Tree是很多棵Splay Tree的整合,这个是线段树。

        同样,在高中的OI生涯中,经常听到某人说这个名词。然后也大致的知道是很多颗线段树弄在一起,每一棵树维护一个字区间(那时已经学了线段树),当时就想,一颗线段树的代码量已经很大了,那么多棵树在一起那将会是多么巨大的代码量……然后就敬而远之。最后却发现,代码量比线段树本身还要短……

        先来一段发明人的话吧:

这个东西是当初我弱不会划分树的时候写出来替代的一个玩意..被一小撮别有用心的人取了很奇怪的名字> <
想法是对原序列的每一个前缀[1..i]建立出一颗线段树维护值域上每个数的出现次数,然后发现这样的树是可以减的,然后就没有然后了

多么可爱的发明人……

        主席树主要是解决满足区间减法的最优解问题。正如他所说,对于每一个前缀[1..i],我们都建立一颗线段树,这棵树维护在这个区间上的数字个数。当然,并不是简单的维护区间长度,我们要先离散化。对于离散后的数据,每一棵树,我们保存前缀区间[1..i]中所有小于i的数字的个数。那么,现在问题来了,如果真的每棵线段树都按照单棵树那么建立,那么很明显会MLE。所以我们必须控制空间使用,注意到,第i棵树与第i+1棵树不同之处在于多了第i+1个点,那么我们就可以这样做:

                1、如果第i+1个数大于区间的mid,那么显然前后两棵树的左区间是一样的,那么便共享

                2、如果第i+1个数小于等于区间的mid,那么前后两棵树的右区间相同,同样也是共用

        这样我们就可以把空间复杂度讲到一个比较能接受的范围了,经(wo)计(bu)算(hui),我们发现,空间大概为最大线段树的5倍。具体的我们还是看代码吧,这是个很神奇的实现方式哦:

inline void insert(int num,int &i,int l,int r)                     //&的神奇功能,会把外面传入变量一起改变 
{
	tree[T_cnt++]=tree[i];                                         //先继承上一棵树的信息 
	i=T_cnt-1;                                                     //修改子树关系 
	++tree[i].sum;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (num<=mid) insert(num,tree[i].l,l,mid);                     //小于等于mid则在左边更新,右边直接与上一棵树共用
	         else insert(num,tree[i].r,mid+1,r);                   //大于mid,左边更新,右边共用
} 

        建树完毕,然后我们就可以求解问题了。我们以求解静态区间第k大为例。每次询问,求给定区间[l,r]的第k大。有了主席树后,我们查询时只需要判断,第l-1棵树的左边的数字个数与第r棵树的左边数字之差,若大于t,则在两棵树的右边找,否则在左边找。具体代码:

inline int query(int i,int j,int k,int l,int r)                    //查询区间[i,j]内第k大数字 
{
	if (l==r) return l;
	int t=tree[tree[j].l].sum-tree[tree[i].l].sum;
	int mid=(l+r)>>1;
	if (k<=t) return query(tree[i].l,tree[j].l,k,l,mid);		   //二分查询	
	     else return query(tree[i].r,tree[j].r,k-t,mid+1,r); 
}
         现在我来发表一下个人意见,虽然这个东西好像很巧妙,但是我觉得好像用处不大,毕竟想求解区间第k大这种东西好像真的很少碰到吧……然后虽然巧妙地优化了,但是还是不能令人满意啊。好咯,可能是我弱,不知道这个有什么大用,怪我吧……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值