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

可持久化数据结构介绍

 

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

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

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

插入前

插入后

 

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

 

 

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

有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]的右子树, 然后子树的构造也是这个道理。

 

poj2104

 

#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 = 1000010;
int a[N], t[N];
//T[i] 保存第i棵线段树的根
int T[N], lson[N * 30], rson[N * 30], c[N * 30];
int n, m, q, tot;
int build(int l, int r)
{
    int root = tot++;
    c[root] = 0;
    if (l != r)
    {
        int mid = (l + r) >> 1;
        lson[root] = build(l, mid);
        rson[root] = build(mid + 1, r);
    }
    return root;
}

//root是第i棵线段树的根, 现在构造第i+1棵树, pos是a[i+1]所要插入的位置
int update(int root, int pos)
{
    int newRoot = tot++, tmp = newRoot;
    int l = 1, r = m;
    c[newRoot] = c[root] + 1;
    while (l < r)
    {
        int mid = (l + r) >> 1;
        if (pos <= mid)//要插入的位置位于左子树, 要么右子树可以指向第i棵线段树的右子树
        {
            r = mid;
            lson[newRoot] = tot++;
            rson[newRoot] = rson[root];
            newRoot = lson[newRoot];//继续构造左子树
            root = lson[root];
        }
        else
        {
            l = mid + 1;
            lson[newRoot] = lson[root];
            rson[newRoot] = tot++;
            newRoot = rson[newRoot];
            root = rson[root];
        }
        c[newRoot] = c[root] + 1;//该子树保存的值的个数+1
    }
    return tmp;
}

int hs(int x)
{
    return lower_bound(t + 1, t + m + 1, x) - t;
}

int query(int leftroot, int rightroot, int k)
{
    int l = 1, r = m;
    while (l < r)
    {
        int mid = (l + r) >> 1;
        if (c[lson[rightroot]] - c[lson[leftroot]] >= k)
        {
            r = mid;
            rightroot = lson[rightroot];
            leftroot = lson[leftroot];
        }
        else
        {
            l = mid + 1;
            k -= c[lson[rightroot]] - c[lson[leftroot]];
            rightroot = rson[rightroot];
            leftroot = rson[leftroot];
            
        }
    }
    return l;
}
int main()
{
    while (scanf("%d%d", &n, &q) != EOF)
    {
        tot = 0;
        for (int i = 1; i <= n; ++i)
        {
            scanf("%d", &a[i]);
            t[i] = a[i];
        }
        sort(t + 1, t + n + 1);
        m = unique(t + 1, t + n + 1) - t - 1;
        T[0] = build(1, m);
        for (int i = 1; i <= n; ++i)
        {
            int pos = hs(a[i]);
            //第i棵线段树由第i-1棵线段树修改而来
            T[i] = update(T[i - 1], pos);
        }
        while (q--)
        {
            int l, r, k;
            scanf("%d%d%d", &l, &r, &k);
            printf("%d\n", t[query(T[l - 1], T[r], k)]);
        }

    }
    return 0;
}

 

posted @ 2015-07-15 10:35  justPassBy  阅读(244)  评论(0编辑  收藏  举报