【Coel.算法笔记】【主席树入门】【模板】可持久化线段树 2

题前闲话

Sherlockk 大佬又开比赛了,然而不会做。
有空切水题还不如多学点东西(暴论)

可持久化线段树 2

可持久化线段树的题目有两个,其中第二个的应用场景更广,所以讲这个。
洛谷传送门

题目描述

如题,给定 n 个整数构成的序列 a,将对于指定的闭区间 [l,r] 查询其区间内的第 k 小值。

输入格式

第一行包含两个整数,分别表示序列的长度 n 和查询的个数 m
第二行包含 n 个整数,第 i 个整数表示序列的第 i 个元素 ai
接下来 m 行每行包含三个整数 l,r,k , 表示查询区间 [l,r] 内的第 k 小值。

输出格式

对于每次询问,输出一行一个整数表示答案。

基本思路

本题为主席树的一个应用:静态查询第 k 小值。
顺便说一句:主席树这个名字来源于它的发明者黄嘉泰的名字缩写 HJT。由此可见这个数据结构已经是十几年前的产物了
联想到之前学过的权值线段树,我们有一个很直观的想法:
开设 n 个权值线段树,其中 i 号权值线段树存放区间 [1,i] 中各数字的出现情况。由于权值线段树满足可加减性,所以区间 [l,r] 可以通过 [1,l1][1,r] 合并得来。这样,我们就可以同时查询两个权值树,通过递归寻找到适合的数实现查询。

但这样做空间消耗十分巨大,所以我们采用一个特殊方法:动态开点
举一个很显然的例子:假设我们要修改的结点位于左子树,那么右子树是完全不会发生变化的。也就是说,我们只需要给左子树开设节点,而右子树直接指向先前的右子树即可。
借用一下 OI-Wiki 的图:
红色为修改后受到影响的结点,白色为旧结点。

代码实现

先对原数组做一个离散化,否则动态开点也还是会爆内存。

void init_hash() {
    memcpy(t, a, sizeof(a));
    sort(t + 1, t + n + 1);
    top = unique(t + 1, t + n + 1) - t - 1;
    for (int i = 1; i <= n; i++)
        a[i] = lower_bound(t + 1, t + top + 1, a[i]) - t;
}

然后对每个节点做修改操作。传入时要提供先前操作的树,方便指向。

void pushup(int id) {
    s[id].v = s[s[id].ls].v + s[s[id].rs].v;
}

void modify(int lsid, int& id, int l, int r, int k, int v) {
    if (!id) //到底了,分配新结点
        id = ++cnt;
    if (l == r)
        return s[id].v += v, (void)Miolic;
    int mid = (l + r) >> 1;
    if (k <= mid) { //权值在左,指向右儿子并修改左儿子
        s[id].rs = s[lsid].rs;
        s[id].ls = ++cnt;
        s[s[id].ls] = s[s[lsid].ls];
        modify(s[lsid].ls, s[id].ls, l, mid, k, v);
    } else { //权值在右,指向左儿子并修改右儿子
        s[id].ls = s[lsid].ls;
        s[id].rs = ++cnt;
        s[s[id].rs] = s[s[lsid].rs];
        modify(s[lsid].rs, s[id].rs, mid + 1, r, k, v);
    }
    pushup(id);//标记上传
    }

最后查询。

nt query(int id1, int id2, int l, int r, int k) {
    if (l == r)//到底了,返回
        return l;
    int mid = (l + r) >> 1, tp = s[s[id2].ls].v - s[s[id1].ls].v;//tp 为当前小于等于 mid 的元素个数
    if (tp >= k)//大了往左找
        return query(s[id1].ls, s[id2].ls, l, mid, k);
    else//小了往右找,还得像平衡树一样减去当前数字
        return query(s[id1].rs, s[id2].rs, mid + 1, r, k - tp);
}

代码如下:

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>

namespace __Coel_FastIO {
#ifndef LOCAL
#define _getchar_nolock getchar_unlocked
#define _putchar_nolock putchar_unlocked
#endif
int read() {
    int x = 0, f = 1;
    char ch = _getchar_nolock();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = _getchar_nolock();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = _getchar_nolock();
    }
    return x * f;
}
void write(int x, char Ctr_sign) {
    if (x < 0) {
        x = -x;
        putchar('-');
    }
    static int buf[35];
    int top = 0;
    do {
        buf[top++] = x % 10;
        x /= 10;
    } while (x);
    while (top)
        _putchar_nolock(buf[--top] + '0');
    _putchar_nolock(Ctr_sign);
}
}  // namespace __Coel_FastIO

using namespace __Coel_FastIO;
using namespace std;

#define Miolic 0

const int maxn = 1e5 + 10;

struct node {
    int v, ls, rs;
} s[maxn << 8];

int root[maxn << 2], cnt;

int n = read(), m = read(), top, a[maxn << 2];
int t[maxn << 2];

void pushup(int id) {
    s[id].v = s[s[id].ls].v + s[s[id].rs].v;
}

void modify(int lsid, int& id, int l, int r, int k, int v) {
    if (!id)
        id = ++cnt;
    if (l == r)
        return s[id].v += v, (void)Miolic;
    int mid = (l + r) >> 1;
    if (k <= mid) {
        s[id].rs = s[lsid].rs;
        s[id].ls = ++cnt;
        s[s[id].ls] = s[s[lsid].ls];
        modify(s[lsid].ls, s[id].ls, l, mid, k, v);
    } else {
        s[id].ls = s[lsid].ls;
        s[id].rs = ++cnt;
        s[s[id].rs] = s[s[lsid].rs];
        modify(s[lsid].rs, s[id].rs, mid + 1, r, k, v);
    }
    pushup(id);
}

int query(int id1, int id2, int l, int r, int k) {
    if (l == r)
        return l;
    int mid = (l + r) >> 1, tp = s[s[id2].ls].v - s[s[id1].ls].v;
    if (tp >= k)
        return query(s[id1].ls, s[id2].ls, l, mid, k);
    else
        return query(s[id1].rs, s[id2].rs, mid + 1, r, k - tp);
}

void init_hash() {
    memcpy(t, a, sizeof(a));
    sort(t + 1, t + n + 1);
    top = unique(t + 1, t + n + 1) - t - 1;
    for (int i = 1; i <= n; i++)
        a[i] = lower_bound(t + 1, t + top + 1, a[i]) - t;
}

int main(void) {
    for (int i = 1; i <= n; i++)
        a[i] = read();
    init_hash();
    for (int i = 1; i <= n; i++)
        modify(root[i - 1], root[i], 1, top, a[i], 1);
    for (int i = 1; i <= m; i++) {
        int l = read(), r = read(), k = read();
        write(t[query(root[l - 1], root[r], 1, top, k)], '\n');
    }
    return 0;
}

本文作者:Coel's Blog

本文链接:https://www.cnblogs.com/Coel-Flannette/p/16339929.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   秋泉こあい  阅读(17)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑
  1. 1 アイノマテリアル (feat. 花里みのり&桐谷遥&桃井愛莉&日野森雫&MEIKO) MORE MORE JUMP!
アイノマテリアル (feat. 花里みのり&桐谷遥&桃井愛莉&日野森雫&MEIKO) - MORE MORE JUMP!
00:00 / 00:00
An audio error has occurred.