zoukankan      html  css  js  c++  java
  • 主席树

    这个东西对于我这种菜鸡来说还是有一点难以把他解释清楚,所以在一些关键的讲解步骤(我自己解释不清楚的地方),我就只好引用一下一些大佬的!(其实写完了以后发现这些东西都是我自己写的!!!嗯,有进步!!!)

    下面我就开始讲哈!

    相信大家看这篇讲解之前都应该看了我前面写的一篇博客,如果没有看大家可以先去看一下:

    讲解传送门(求整体区间第k大)

    好了我们默认大家应该已经懂得了如何求整体区间第k大了, 其实你只要学会这个了主席树已经就比较简单了,那么我们下面就来讲解一下主席树和这个东西的一些区别,主席树说白了就是比这个东西多了一个我们可以查区间中的第k大,那么这个操作如何实现呢?
    是不是我们要把每个区间都建一棵树呢?其实没有这个必要,我们还是按照前缀建树,这样会从n方的空间复杂度成功减少到n的复杂度,但是这样很明显空间上还是不满足要求,但是我们同样又发现其实如果我们这样进行建树会有很多地方有重复,所以我们其实可以充分的利用一下这些重复使用的空间,也就是说我们下一次建树就没有必要再重新建这一部分了,我们只是需要把这部分没有的部分重新建进去(也就是每一次多建一条链进入树中),然后把每一个节点记录下来(开一个数组进行记录),然后建树的这一部分我们就简单而粗暴的讲解完了。

    下面给大家一个建树的代码(不需要完全理解,有一些细节每一个人处理的都不一样):

    void ins(int k,long long l,long long r,int val) { 
    	a[k].l=l,a[k].r=r;
    	if(l==r) { 
    		a[k].cnt++;return;
    	} 
    	long long mid=l+r;mid/=2;
    	if(val<=mid) { 
    		cnt++;
    		a[cnt]=a[ls(k)];
    		ls(k)=cnt;
    		ins(ls(k),l,mid,val);
    	} 
    	else { 
    		cnt++;
    		a[cnt]=a[rs(k)];
    		rs(k)=cnt;
    		ins(rs(k),mid+1,r,val);
    	} 
    	update(k);
    } 
    

    然后建完树了以后,我们就要进行处理操作了,这部分操作其实才是整片代码中最难理解的地方,大家一定要认真的理解一下,我这里尽量用最少的话语把大家讲懂!想要理解这一部分内容请确保你一定是理解到了我们每一次建树其实只是往树中增加(修改)一条链,所以说如果说我们要查询i到j之间的第k大,那么这棵树从哪里来呢?其实就是我们把j的前缀树减去i的前缀树就是ij区间的树,所以如果我们用j左子树的大小减去i左子树的大小那么我们就成功的求出了ij树左子树的大小,然后我们就可以进行与上一篇讲解同样的事情了,如果我们要找的k大于左子树,那么我们就找右子树,否则继续找左子树,最后就找到了最小,简不简单!!!(还是不懂的继续往下看,careful!!!)

    如果你还是没有看懂,那么我在这里给你两个小小的提示,首先,你要注意,为什么这里的两个树可以互相的进行减呢?是因为,这个树作为一个权值线段树他是长得一模一样的。所以我们相减之后其实那些剩余的桶就是在这个区间中增加的元素个数!!!其次,你也可能会问,为什么query中必须写成右树的左边等于右树的右边,其实是这样的,因为我们是使用动态开点来建造的权值线段树,所以我们不敢保证左边的树一定会有右边树的节点(因为有可能还没有建造当前节点)哈哈哈,懂了吧!不懂就不是我的错了!!!

    下面上一个代码:

    #include<bits/stdc++.h>
    using namespace std;
    struct sd{
    	int l,r,son[2],cnt;
    };
    const int Max=1e9+7;
    const int maxn=2e5+10;
    int cnt=1;
    int n,m;
    int rot[maxn],num[maxn];
    sd node[maxn*50];
    void update(int k)
    {
    	node[k].cnt=node[node[k].son[0]].cnt+node[node[k].son[1]].cnt;
    }
    void modify(int k,long long l,long long r,int val)
    {
    	node[k].l=l;node[k].r=r;
    	if(l==r)
    	{
    		node[k].cnt++;return;
    	}
    	long long mid=l+r;mid/=2;
    	if(val<=mid)
    	{
    		cnt++;
    		node[cnt]=node[node[k].son[0]];
    		node[k].son[0]=cnt;
    		modify(node[k].son[0],l,mid,val);
    	}
    	else
    	{
    		cnt++;
    		node[cnt]=node[node[k].son[1]];
    		node[k].son[1]=cnt;
    		modify(node[k].son[1],mid+1,r,val);
    	}
    	update(k);
    }
    int query(int rl,int rr,int val)
    {
    	if(node[rr].l==node[rr].r) return node[rr].l;//very very important
    	int delta=node[node[rr].son[0]].cnt-node[node[rl].son[0]].cnt;
    	if(val<=delta)
    	return query(node[rl].son[0],node[rr].son[0],val);
    	else
    	return query(node[rl].son[1],node[rr].son[1],val-delta);
    }
    int main()
    {
    	std::ios::sync_with_stdio(false);
    	cin>>n>>m;
    	for(int i=1;i<=n;++i)
    	{
    		cin>>num[i]; 
    	}
    	rot[0]=1;
    	for(int i=1;i<=n;++i)
    	{
    		cnt++;
    		rot[i]=cnt;node[rot[i]]=node[rot[i-1]];//复制一个新的节点 
    		modify(rot[i],0,2*Max,num[i]+Max);
    	}
    	int a,b,c;
    	for(int i=1;i<=m;++i)
    	{
    		cin>>a>>b>>c;
    		cout<<query(rot[a-1],rot[b],c)-Max<<endl;
    	}
    	return 0;
    }
    

    谢谢采纳!!!

  • 相关阅读:
    【BZOJ】2209: [Jsoi2011]括号序列(splay)
    【BZOJ】1251: 序列终结者(splay)
    【UOJ】【UR #2】猪猪侠再战括号序列(splay/贪心)
    【BZOJ】3781: 小B的询问(莫队算法)
    【BZOJ】3289: Mato的文件管理(莫队算法+树状数组)
    【BZOJ】2038: [2009国家集训队]小Z的袜子(hose)(组合计数+概率+莫队算法+分块)
    【SPOJ】7258. Lexicographical Substring Search(后缀自动机)
    vue 中 event.stopPropagation() 和event.preventDefault() 使用
    Vue 中 使用v-show
    Vue v-if以及 v-else 的使用
  • 原文地址:https://www.cnblogs.com/mudrobot/p/13328381.html
Copyright © 2011-2022 走看看