zoukankan      html  css  js  c++  java
  • 主席树(可持久化线段树) 静态第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
    ", t[query(T[l - 1], T[r], k)]);
            }
    
        }
        return 0;
    }
  • 相关阅读:
    洛谷P1022计算器的改良(字符串+各种细节坑点考虑)
    hdu5974Math Problem(数学,思维,公式,取巧猜)
    牛客练习赛26A平面(数学公式)
    洛谷P1217回文质数(特判筛选,取巧判断顺序)
    尺取法
    51nod1006最长公共子序列(lcs输出路径)
    51nod1175区间第k大(小)数(主席树模板)
    51nod1174区间中最大的数(rmq模板或线段树 && 线段树标准模板)
    51nod1094和为k连续区间(前缀和+map判断优化)
    矩阵快速幂求递推数列
  • 原文地址:https://www.cnblogs.com/justPassBy/p/4647624.html
Copyright © 2011-2022 走看看