码迷,mamicode.com
首页 > 其他好文 > 详细

主席树(可持久化线段树) 静态第k大

时间:2015-07-15 11:05:36      阅读:243      评论:0      收藏:0      [点我收藏+]

标签:

可持久化数据结构介绍

 

可持久化数据结构是保存数据结构修改的每一个历史版本,新版本与旧版本相比,修改了某个区域,但是大多数的区域是没有改变的,

所以可以将新版本相对于旧版本未修改的区域指向旧版本的该区域,这样就节省了大量的空间,使得可持久化数据结构的实现成为了可能。

如下图,就是可持久化链表

插入前

技术分享

插入后

技术分享

 

尽可能利用历史版本和当前版本的相同区域来减少空间的开销。

 

 

而主席树(可持久化线段树)的原理同样是这样。

有n个数字,  我们将其离散化,那么总有[1,n]个值,如果建一棵线段树,每个结点维护子树中插入的值的个数。 求总区间第k大

那么如果k>=左子树中值的个数,那么就去左子树中找,   否则就去右子树中找第(k-左子树值的个数)大值。

如果要求区间[l,r]第k大,那么就要维护n棵线段树

每棵线段树维护的都是[1,n]值出现的个数, 所以每棵线段树的形态结构是相同的

第i棵线段树的根为T[i] , 维护的是前i个数字离散化后出现的次数。

 

那么主席树有两个性质

线段树的每个结点,保存的都是这个区间含有的数字的个数

主席树的每个结点,也就是每棵线段树的大小和形态是一样的,也就是主席树的每个结点(线段树与线段树之间)是可以相互进行加减运算的

假设要求区间[l,r]的第k大, T[r]左子树值的个数 - T[l-1]左子树值的个数大于>=k  ,  那么就说明  区间[l,r]的数离散化后有大于n个数插入了左子树,

所以应该去左子树去找第k个大, 反之,去右子树找 第(k- (T[r]左子树值的个数 - T[l-1]左子树值的个数) )大。

 

至于主席树的构建, 第T[i+1]棵树相比第T[i]棵树,多插入了一个数字, 也就相当于修改了一条链。

如果第a[i+1]个数插入了T[i+1]的左子树, 那么T[i+1]的右子树和T[i]的右子树是一样的, 所以可以直接指向T[i]的右子树, 然后子树的构造也是这个道理。

 

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
/*
可持久化线段树,函数式线段树,主席树
动态第k大

线段树维护的是值出现的次数
*/
const int N = 100010;
int a[N], t[N];
int T[N * 30], lson[N * 30], rson[N * 30], c[N * 30];
int total, n, q, m;
void initHash()
{
    for (int i = 1; i <= n; ++i)
        t[i] = a[i];
    sort(t + 1, t + n + 1);
    m = unique(t + 1, t + n + 1) - t - 1;

}

int Hash(int x)
{
    return lower_bound(t + 1, t + m + 1, x) - t;
}
int build(int l, int r)//建一棵区间长度为m的线段树,  每个节点的值都是0
{
    int root = total++;
    c[root] = 0;
    if (l != r)
    {
        int mid = (l + r) >> 1;
        lson[root] = build(l, mid);
        rson[root] = build(mid + 1, r);
    }
    return root;
}
// T[i] 和T[i+1]相比, 只更新了一条链,  所以可以借用T[i+1]的一些子树来节省空间
// 按下标建树?
int update(int root, int pos, int val)
{
    int newRoot = total++, tmp = newRoot;
    c[newRoot] = c[root] + val;
    int l = 1, r = m;
    while (l < r)
    {
        int mid = (l + r) >> 1;
        if (pos <= mid)//如果进入左子树, 那么右子树可以指向旧版本
        {
            lson[newRoot] = total++;
            rson[newRoot] = rson[root];
            newRoot = lson[newRoot];
            root = lson[root];
            r = mid;
        }
        else//左子树指向旧版本
        {
            rson[newRoot] = total++;
            lson[newRoot] = lson[root];
            newRoot = rson[newRoot];
            root = rson[root];
            l = mid + 1;
        }
        c[newRoot] = c[root] + val;
    }
    return tmp;
}
//每次插入都记录,它是插入左区间呢,还是右区间,  那么询问区间第k大时,只要该区间进入左区间的数字大于等于k,那么就去左子树找,
//否则, 缩小k,  去右区间找
int query(int leftRoot, int rightRoot, int k)
{
    int l = 1, r = m;
    while (l < r)
    {
        int mid = (l + r) >> 1;
        //c[lson[leftRoot]] - c[lson[rightRoot]] 表示区间内的数进入到左区间的有多少个
        if (c[lson[leftRoot]] - c[lson[rightRoot]] >= k)
        {
            r = mid;
            leftRoot = lson[leftRoot];
            rightRoot = lson[rightRoot];
        }
        else
        {
            l = mid + 1;
            k -= c[lson[leftRoot]] - c[lson[rightRoot]];
            leftRoot = rson[leftRoot];
            rightRoot = rson[rightRoot];
        }
    }
    return l;
}
int main()
{
    int  L, R, k;
    while (scanf("%d%d", &n, &q) == 2)
    {
        total = 0;
        for (int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        initHash();
        T[n + 1] = build(1, m);
        for (int i = n; i; i--)
        {
            int pos = Hash(a[i]);
            T[i] = update(T[i + 1], pos, 1);
        }
        while (q--)
        {
            scanf("%d%d%d", &L, &R, &k);
            printf("%d\n", t[query(T[L], T[R + 1], k)]);
        }
    }
    return 0;
}

 

主席树(可持久化线段树) 静态第k大

标签:

原文地址:http://www.cnblogs.com/justPassBy/p/4647624.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!