( 数据结构专题 )【 主席树 】

( 数据结构专题 )【 主席树 】

推荐阅读:https://blog.csdn.net/bestFy/article/details/78650360

推荐阅读:https://blog.csdn.net/qq_39565901/article/details/81782739

推荐阅读:https://blog.csdn.net/chenxiaoran666/article/details/81501461

让我们从权值线段树开始说起

要学主席树,我们就要先学权值线段树。

权值线段树的区间存的并不是节点信息,而是在值在某一范围内的数的个数,也就是一个桶树。

不会的可以先看这一篇,( 数据结构专题 )【 权值线段树 】

从权值线段树到主席树

知道了权值线段树,我们就可以开始尝试实现主席树了。

我们已经了解到在权值线段树中可以快速找到整个区间内第k大的数,那么我们怎么在自定义区间内找k大的值呢?

答:假设我们现在有5个数,那么我们每次插入一个数就保存当前权值线段树的状态,一共保存5个权值线段树。

然后我们发现,如果要求[2, 5]内某个范围内数的个数,就把第5棵数和第1棵数相减,的到的新树就是[2,5]范围的了,相应的就可以找区间的第k大了。

思考:那么我们没有这么多空间建树怎么办?

上图,左边黑线画的是插入了两个点的权值线段树, 右边画的是插入了三个点的权值线段树,容易看出其实只有在插入点往上的路径的值发生了变化,所以我们可以只保存这条路径的值,如左图蓝色的线,这样空间的问题就解决了,在开点的时候注意需要连那些点就好了,写代码可以辅助这张图写。

因为主席树不满足儿子节点和父亲节点2*node和2*node+1的关系,所以单独开数组记录每个节点的儿子节点。

 

 

 


例题1: Kth number  HDU - 2665

题意:无序给n个数,找任意区间内第k小的值。

下面代码是找第k小的,求第k大的可以根据权值线段树的性质改一改就可以了。

代码:

#include <bits/stdc++.h>
#define mid (left+right)/2

using namespace std;

const int maxn=1e5+10;
int n,m,q,tot=0;
int a[maxn],b[maxn];
int T[maxn],tree[maxn*20],L[maxn*20],R[maxn*20];
// T[i]存的是第i棵树的root, tree[i]存的是正常线段树的值, l[i]存的是i号节点的左儿子

int built_tree( int left, int right )
{
    int node = tot++;
    if ( left<right ) {
        L[node] = built_tree(left,mid);
        R[node] = built_tree(mid+1,right);
    }
    return node;
}

int update( int pre, int left, int right, int x )
{
    int node = tot++;
    L[node] = L[pre];  // 必须要加
    R[node] = R[pre];
    tree[node] = tree[pre] + 1;
    if ( left<right ) {
        if ( x<=mid ) L[node] = update(L[pre],left,mid,x);
        else R[node] = update(R[pre],mid+1,right,x);
    }
    return node;
}

int query( int node1, int node2, int left, int right, int k )
{
    if ( left==right ) return left;
    int lsum = tree[ L[node2] ] - tree[ L[node1] ];
    if ( lsum>=k ) return query( L[node1], L[node2], left, mid, k );
    else return query( R[node1],R[node2],mid+1,right,k-lsum ); // 注意是k-lsum
}

int main()
{
    int listt;
    cin >> listt;
    while ( listt-- ) {
        tot = 0; // 主席树动态开点,从0开始
        memset(T,0,sizeof(T));memset(tree,0,sizeof(tree));
        memset(L,0,sizeof(L));memset(R,0,sizeof(R));
        cin >> n >> q;
        for ( int i=1; i<=n; i++ ) {
            scanf("%d",&a[i]); b[i]=a[i];
        }
        sort(b+1,b+1+n);
        m = unique(b+1,b+1+n) - b - 1; // 离散化
        T[0] = built_tree(1,m);
        for ( int i=1; i<=n; i++ ) {
            a[i] = lower_bound(b+1,b+1+m,a[i]) - b;
            T[i] = update(T[i-1],1,m,a[i]);
        }
        while ( q-- ) {
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            int p = query(T[x-1],T[y],1,m,z);
            printf("%d\n",b[p]);
        }
    }

    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值