【主席树】洛谷 P3834 【模板】可持久化线段树 1(主席树)

洛谷 P3834 【模板】可持久化线段树 1(主席树)

这是个非常经典的主席树入门题——静态区间第 k 小

题意:给定 n 个整数构成的序列,将对于指定的闭区间查询其区间内的第 k 小值。

主席树

预备知识:线段树、权值线段树、前缀和思想、离散化

(写在前:主席树真的理解了很久,我的感觉就是:如果真的怎么都想不明白那就看着神犇的代码自己敲一遍(当然要选择近似自己码风的代码,我找了n久就没找到,结果后来是看着好几篇博客(我都不怎么喜欢的代码实现),还时不时问学长才算敲完自己的模板。)当然这个的前提是自己理解了它的涵义,只是不会怎么实现。接下来就可以调试了。将样例的实现过程手动模拟一遍画下来,然后调试。看看究竟是如何实现的。不断模拟调试的过程会对主席树的理解越发的深刻。这是我的切身感受。)

主席树的核心思想就是继承上一棵树的信息。

(持续更新详解……)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define INF 0x3f3f3f3f
#define lowbit(x) x & (-x)

#define MID (l + r ) >> 1
#define lsn rt << 1
#define rsn rt << 1 | 1
#define Lson lsn, l, mid
#define Rson rsn, mid + 1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define eps  1e-6

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxN = 2e5 + 5;
int n, m;
vector<int>vt, disc;
int root[maxN], tot;//根结点标号//新结点标号
struct node{//tree[i]:结点标号为i的结点
    int ls, rs, num;//左儿子标号//右儿子标号//子树上的结点个数
    node(int a = 0, int b = 0, int c = 0): ls(a), rs(b), num(c){}
}tree[maxN * 20];//log2(1e6) = 6 * 3.3 < 20
void update(int &now, int pre, int l, int r, int k)//新结点标号//引用就是为了能从儿子结点回溯给其父亲结点
{
    now = ++ tot;//新结点标号//回溯给它的父亲结点(即更新now的父亲结点的儿子结点标号)
    //先继承上一棵对应子树的左右儿子
    tree[now].ls = tree[pre].ls;
    tree[now].rs = tree[pre].rs;
    //旗下的结点个数+1
    tree[now].num = tree[pre].num + 1;
    if(l == r) return;//找到所在的位置,返回
    int mid = MID;
    if(mid >= k) update(tree[now].ls, tree[pre].ls, l , mid, k);
    else update(tree[now].rs, tree[pre].rs, mid + 1, r, k);
}
int query(int i, int j, int l, int r, int k)
{
    int dif = tree[tree[j].ls].num - tree[tree[i].ls].num;//左儿子区间元素个数
    int mid = MID;
    if(l == r) return l;//找到第k小的权值,返回
    if(dif >= k)//左儿子[l, r]区间中的元素个数大于等于k个,那第k小一定在左区间
        return query(tree[i].ls, tree[j].ls, l, mid, k);
    else//否则,第k小的数是右儿子区间中第k - dif小的数
        return query(tree[i].rs, tree[j].rs, mid + 1, r, k - dif);
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 0; i < n; i ++ )
    {
        int a; scanf("%d", &a);
        vt.push_back(a);
        disc.push_back(a);
    }
    sort(disc.begin(), disc.end());
    disc.erase(unique(disc.begin(), disc.end()), disc.end());
    int UP = disc.size();//离散化后权值线段树的上限
    for(int i = 1; i <= n; i ++ )
    {
        int th = lower_bound(disc.begin(), disc.end(), vt[i - 1]) - disc.begin() + 1;
        //第i棵线段树更新
        update(root[i], root[i - 1], 1, UP, th);//输入的第i个数是权值线段树中第th个数,更新它
    }
    while(m -- )
    {
        int l, r, k; scanf("%d%d%d", &l, &r, &k);
        printf("%d\n", disc[query(root[l - 1], root[r], 1, UP, k) - 1]);
    }
    return 0;
}
发布了180 篇原创文章 · 获赞 54 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44049850/article/details/103974411
今日推荐