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

题目背景

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

数据已经过加强,请使用主席树。同时请注意常数优化

题目描述

如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。

输入格式:

第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。

第二行包含N个正整数,表示这个序列各项的数字。

接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间 [l, r][l,r] 内的第k小值。

输出格式:

输出包含k行,每行1个正整数,依次表示每一次查询的结果

输出样例#1:

5 5
25957 6405 15770 26287 26465 
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1

输出样例#1:

6405
15770
26287
25957
26287

说明

数据范围

对于20%的数据满足: 1 \leq N, M \leq 101≤N,M≤10

对于50%的数据满足: 1 \leq N, M \leq 10^31≤N,M≤103

对于80%的数据满足: 1 \leq N, M \leq 10^51≤N,M≤105

扫描二维码关注公众号,回复: 2411161 查看本文章

对于100%的数据满足: 1 \leq N, M \leq 2\cdot 10^51≤N,M≤2⋅105

对于数列中的所有数 a_iai ,均满足 -{10}^9 \leq a_i \leq {10}^9−109≤ai≤109

样例数据说明

N=5,数列长度为5,数列从第一项开始依次为 [25957, 6405, 15770, 26287, 26465 ][25957,6405,15770,26287,26465]

第一次查询为 [2, 2][2,2] 区间内的第一小值,即为6405

第二次查询为 [3, 4][3,4] 区间内的第一小值,即为15770

第三次查询为 [4, 5][4,5] 区间内的第一小值,即为26287

第四次查询为 [1, 2][1,2] 区间内的第二小值,即为25957

第五次查询为 [4, 4][4,4] 区间内的第一小值,即为26287

思路

既然是主席树的模板题,就大概说一说主席树。

题目的要求是求给定的区间第k大元素,首先如果这道题不是给定区间[L,R],而是求一棵权值线段树上的第k大,这个的求法是什么,这样就比较容易了,就是这个题HDU2852 KiKi’s K-Number(值域线段树,求线段树第k大),在线段树上叶子节点表示这个节点的值出现了几次,然后就像普通的线段树一样更新上去,要查找第k大的时候,就从根节点出发先看左子树的节点数x是否大于等于k,如果大于等于k就向左子树找,如果小于k,就去右子树找,去右子树找的时候k就变成了x-k,最后就可以找到第k大的位置。

主席树是什么,主席树也被称做可持久化线段树,它采取动态建树的方式建了一个有n个节点的线段树,由于是动态开点,动态建树,所以复杂度是 O ( n l g n ) ,建立方法是首先建立一棵空树,然后依次把给出序列的值插入这棵空树,每插入一个值,就新开一个根节点建立线段树,第i个线段树存储的信息是区间[1,i]的信息,所以面对询问求[l,r]的第k大,我们就可以转化成sum[r]-sum[l-1]类似前缀和相减的方式来把[1,r][1,l-1]这两棵线段树进行相减,然后求第k大的时候也是边递归边求第k大。
详细的讲解请参考这个博客:主席树详解

代码

#include <cstdio>
#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <sstream>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
#include <list>
using namespace std;
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
const int N = 2e5 + 10;
const int inf = 0x3f3f3f3f;
int node_cnt, n, m;
int sum[N << 5], rt[N], lc[N << 5], rc[N << 5];
int a[N], b[N], p; //原序列和离散序列和修改点
void build(int &t, int l, int r)
{
    t = ++node_cnt;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    build(lc[t], l, mid);
    build(rc[t], mid + 1, r);
}
int modify(int o, int l, int r)
{
    int oo = ++node_cnt;
    lc[oo] = lc[o];
    rc[oo] = rc[o];
    sum[oo] = sum[o] + 1;
    if (l == r)
        return oo;
    int mid = (l + r) >> 1;
    if (p <= mid)
        lc[oo] = modify(lc[oo], l, mid);
    else
        rc[oo] = modify(rc[oo], mid + 1, r);
    return oo;
}

int query(int u, int v, int l, int r, int k) //求u,v这两棵线段树的差的树中的第k大
{
    int mid = (l + r) >> 1, ans;
    int x = sum[lc[v]] - sum[lc[u]];
    if (l == r)
        return l;
    if (x >= k)
        ans = query(lc[u], lc[v], l, mid, k);
    else
        ans = query(rc[u], rc[v], mid + 1, r, k - x);
    return ans;
}
void init()
{
    node_cnt = 0;
}
int main()
{
    //freopen("in.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    init();
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        b[i] = a[i];
    }
    sort(b + 1, b + n + 1);
    int q = unique(b + 1, b + n + 1) - (b + 1);
    build(rt[0], 1, q);
    for (int i = 1; i <= n; i++)
    {
        p = lower_bound(b + 1, b + q + 1, a[i]) - b;
        rt[i] = modify(rt[i - 1], 1, q);
    }
    int l, r, k;
    while (m--)
    {
        scanf("%d%d%d", &l, &r, &k);
        int ans = query(rt[l - 1], rt[r], 1, q, k);
        printf("%d\n", b[ans]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/riba2534/article/details/81136779