主席树(可持久化线段树) 静态第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]的右子树, 然后子树的构造也是这个道理。

#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;
}
时间: 2024-10-23 19:34:14

主席树(可持久化线段树) 静态第k大的相关文章

主席树/函数式线段树/可持久化线段树

什么是主席树 可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本.同时充分利用它们之间的共同数据来减少时间和空间消耗. 因此可持久化线段树也叫函数式线段树又叫主席树. 可持久化数据结构 在算法执行的过程中,会发现在更新一个动态集合时,需要维护其过去的版本.这样的集合称为是可持久的. 实现持久集合的一种方法时每当该集合被修改时,就将其整个的复制下来,但是这种方法会降低执行速度并占用过多的空间. 考虑一个持久集合S. 如图所示,对集合的

主席树 | | 可持久化线段树

可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本.同时充分利用它们之间的共同数据来减少时间和空间消耗. 所以这里讲的可持久化线段树也叫函数式线段树(又叫主席树……因为先驱就是fotile主席Orz……). 先了解一下主席树 http://seter.is-programmer.com/posts/31907.html    很详细的介绍了函数式线段树(主席树). 主席树其实就是很多棵线段树,由于每次更新只需要更新logN个节点,所

权值线段树&amp;&amp;可持久化线段树&amp;&amp;主席树

权值线段树 顾名思义,就是以权值为下标建立的线段树. 现在让我们来考虑考虑上面那句话的产生的三个小问题: 1. 如果说权值作为下标了,那这颗线段树里存什么呢? ----- 这颗线段树中, 记录每个值出现的次数 2.权值很大怎么办?数组空间不够啊 ----- 可以先离散化,再记录 3.那权值线段树到底是用来干嘛的呢? ----- 可以快速求出第k小值(其实主要还是为了主席树做铺垫啦) 那第k小值该怎么求呢??? 从树根依次往下 若当前值K大于左儿子的值,则将K-=左儿子的值,然后访问右儿子 若当前

归并树 划分树 可持久化线段树(主席树) 入门题 hdu 2665

如果题目给出1e5的数据范围,,以前只会用n*log(n)的方法去想 今天学了一下两三种n*n*log(n)的数据结构 他们就是大名鼎鼎的 归并树 划分树 主席树,,,, 首先来说两个问题,,区间第k大 ,,,, 这个问题的通用算法是 划分树,, 说白一点就是把快速排序的中间结果存起来, 举个栗子 原数列 4 1 8 2 6 9 5 3 7 sorted 1 2 3 4 5 6 7 8 9 ........................... qs[0] 4 1 8 2 6 9 5 3 7 q

[POJ2104/HDU2665]Kth Number-主席树-可持久化线段树

Problem Kth Number Solution 裸的主席树,模板题.但是求k大的时候需要非常注意,很多容易写错的地方.卡了好久.写到最后还给我来个卡空间. 具体做法参见主席树论文<可持久化数据结构研究>. AC Code #include "cstdio" #include "iostream" #include "cstring" #include "algorithm" using namespace

[TS-A1505] [清橙2013中国国家集训队第二次作业] 树 [可持久化线段树,求树上路径第k大]

按Dfs序逐个插入点,建立可持久化线段树,每次查询即可,具体详见代码. 不知道为什么,代码慢的要死,, #include <iostream> #include <algorithm> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <ctime> #include <vector> using

BZOJ 2527 Poi2011 Meteors 整体二分+线段树 / 可持久化线段树(MLE)

题目大意:给定一个环,每个节点有一个所属国家,k次事件,每次对[l,r]区间上的每个点点权加上一个值,求每个国家最早多少次操作之后所有点的点权和能达到一个值 首先我们考虑暴力想法 对于每个国家分开讨论 二分操作次数 但是这样每次Judge的时候我们要模拟1~mid所有的操作 浪费在这里的复杂度实在太大 这样做每个国家需要模拟O(klogk)次操作 时间复杂度O(nklogk) TLE 我们需要对浪费在这里的复杂度做一些改进 1.可持久化线段树(MLE) 每次二分一个mid之后 我们要找到mid次

BZOJ 3439 Kpm的MC密码 Trie树+可持久化线段树

题目大意:给定n个字符串,对于每个字符串求以这个字符串为后缀的字符串中第k小的编号 首先将字符串反转 那么就变成了对于每个字符串求以这个字符串为前缀的字符串中第k小的编号 然后考虑对字符串排序 那么对于每个字符串以它为前缀的字符串一定是连续的 那么就转化成了区间第k小 这个用可持久化线段树可以解决 排序自然不能直接排 既然是字符串 考虑Trie树+DFS即可 注意字符串有重复的 小心 #include <vector> #include <cstdio> #include <

spoj3267 D-query 主席树(可持久化线段树)

题目链接 题意:给n个数,m次查询,求[l,r]之间不重复数的个数. 思路:主席树.用一个map记录每个值在当前操作下最新的位置,从前往后插入主席树.对于查询[l,r],窝们在root[ l ]下查询在r之前的不重复数的个数.详见代码: /********************************************************* file name: spoj3267.cpp author : kereo create time: 2015年04月04日 星期六 14时2

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

题目背景 这是个非常经典的主席树入门题——静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示序列的长度和查询的个数. 第二行包含N个正整数,表示这个序列各项的数字. 接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间 [l, r][l,r] 内的第k小值. 输出格式: 输出包含k行,每行1个正整数,依次表示每一次查