洛谷 P3834 【模板】可持久化线段树 2(主席树)
创始人
2024-01-21 18:33:22
0

【模板】可持久化线段树 2

题目背景

这是个非常经典的可持久化权值线段树入门题——静态区间第 kkk 小。

数据已经过加强,请使用可持久化权值线段树。同时请注意常数优化

题目描述

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

输入格式

第一行包含两个整数,分别表示序列的长度 nnn 和查询的个数 mmm。
第二行包含 nnn 个整数,第 iii 个整数表示序列的第 iii 个元素 aia_iai​。
接下来 mmm 行每行包含三个整数 $ l, r, k$ , 表示查询区间 [l,r][l, r][l,r] 内的第 kkk 小值。

输出格式

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

样例 #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

提示

样例 1 解释

n=5n=5n=5,数列长度为 555,数列从第一项开始依次为{25957,6405,15770,26287,26465}\{25957, 6405, 15770, 26287, 26465\}{25957,6405,15770,26287,26465}。

  • 第一次查询为 [2,2][2, 2][2,2] 区间内的第一小值,即为 640564056405。
  • 第二次查询为 [3,4][3, 4][3,4] 区间内的第一小值,即为 157701577015770。
  • 第三次查询为 [4,5][4, 5][4,5] 区间内的第一小值,即为 262872628726287。
  • 第四次查询为 [1,2][1, 2][1,2] 区间内的第二小值,即为 259572595725957。
  • 第五次查询为 [4,4][4, 4][4,4] 区间内的第一小值,即为 262872628726287。

数据规模与约定

  • 对于 20%20\%20% 的数据,满足 1≤n,m≤101 \leq n,m \leq 101≤n,m≤10。

  • 对于 50%50\%50% 的数据,满足 1≤n,m≤1031 \leq n,m \leq 10^31≤n,m≤103。

  • 对于 80%80\%80% 的数据,满足 1≤n,m≤1051 \leq n,m \leq 10^51≤n,m≤105。

  • 对于 100%100\%100% 的数据,满足 1≤n,m≤2×1051 \leq n,m \leq 2\times 10^51≤n,m≤2×105,∣ai∣≤109|a_i| \leq 10^9∣ai​∣≤109,1≤l≤r≤n1 \leq l \leq r \leq n1≤l≤r≤n,1≤k≤r−l+11 \leq k \leq r - l + 11≤k≤r−l+1。

    1、 值域线段树,可持久化线段树。
    cnt[x] 表示 x出现的次数
    2、主席树, 就是n颗线段树叠加。 每新扫描一个数,就增加
    log(n) + 1 个节点。 这些新节点,和原来的某些节点,构成这颗新的线段树。
    3、 求区间 [l, r] 第k小; 利用前缀和。
    4、主席树,插入,查询都需要双指针同步搜索

    5、离散化

#include 
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
using namespace std;
const int N = 2e5 + 10;
int a[N];
int n, m;
vector v;struct node
{int ch[2];int s;	// 节点值域中有多少个数
}tr[N * 22];int root[N], idx; // root[k] 表示第k个数(第k个版本)树的根节点 void build(int &x, int l, int r)	//建一颗空树
{x = ++idx;if(l == r)return;int m = (l + r) >> 1;build(lc(x), l, m);build(rc(x), m + 1, r);
}void insert(int x, int& y, int l, int r, int v)	// x是前一版本的节点指针,y是当前版本的
{y = ++idx; tr[y] = tr[x]; tr[y].s++;if(l == r)return;int m = (l + r) >> 1;	//双指针同步搜索if(v <= m)insert(lc(x), lc(y), l, m, v);elseinsert(rc(x), rc(y), m + 1, r, v);
}//在主席树上查询区间 [l, r] 的第k小
//x是前一版本的指针,y是当前版本的指针
int query(int x, int y, int l, int r, int k)
{if(l == r)return l;int m = (l + r) >> 1;int s = tr[lc(y)].s - tr[lc(x)].s;	//这一步的意义, sum[r] - sum[l - 1]//比如求区间 [L, R]的第k小。 那么,第R颗线段树表示 前R个数出现次数的情况//那么,第L - 1颗线段树表示 前L - 1个数出现次数的情况//s = tr[lc(y)].s - tr[lc(x)].s, 就表示 区间 [L, R] 一共多少个数了if(k <= s)return query(lc(x), lc(y), l, m, k);elsereturn query(rc(x), rc(y), m + 1, r, k - s);
}void discrete()
{sort(v.begin(), v.end());v.erase(unique(v.begin(), v.end()), v.end());
}int getid(int x)	// 返回x的下标
{return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;
}int main()
{scanf("%d%d", &n, &m);for(int i = 1; i <= n; ++i){scanf("%d", &a[i]);v.push_back(a[i]);}discrete();build(root[0], 1, n);for(int i = 1; i <= n; ++i){insert(root[i - 1], root[i], 1, n, getid(a[i]));}int l, r, k;while(m--){scanf("%d%d%d", &l, &r, &k);int id = query(root[l - 1], root[r], 1, n, k)  - 1;printf("%d\n", v[id]);}return 0;
}

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...