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

题目描述

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

输入格式

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

输出格式

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

输入输出样例

输入 #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

解题思路:

可持久线段树是基本线段树的一个简单扩展,它的特点是支持查询历史版本,并且利用历史版本之间的共用数据减少时间和空间的消耗。

它包含的思想有前缀和思想,共用点,离散化,权值线段树,动态开点。

我们在记录权值线段树时,不可能每次都开一颗全新的树进行记录其结点的值。

这是要想到动态开点,把根结点记录好,那个改变的结点重新开辟一块空间记录它和其子节点的值。

用前缀和的思想是因为前面记录的久结点而后面开辟的结点的值之间的差值是新添加的多少个结点。区间[L,R]包含的元素等于区间[1,R]减去区间[1,L-1]。

解题代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int cnt = 0;
int a[N], b[N], root[N];
struct {
	int L, R, sum;
}tree[N<<5];
int update(int pre, int pl, int pr, int x) {
	int rt = ++cnt;
	tree[rt].L = tree[pre].L;
	tree[rt].R = tree[pre].R;
	tree[rt].sum = tree[pre].sum + 1;
	int mid = (pl + pr) >> 1;
	if (pl < pr) {
		if (x <= mid) {
			tree[rt].L = update(tree[pre].L, pl, mid, x);
		}
		else {
			tree[rt].R = update(tree[pre].R, mid + 1, pr, x);
		}
	}
	return rt;
}

int query(int u, int v, int pl, int pr, int k) {
	if (pl == pr) return pl;
	int x = tree[tree[v].L].sum - tree[tree[u].L].sum;
	int mid = (pr + pl) >> 1;
	if (x >= k) {
		return query(tree[u].L, tree[v].L, pl, mid, k);
	}
	else {
		return query(tree[u].R, tree[v].R, mid + 1, pr, k - x);
	}
}
int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		b[i] = a[i];//离散化
	}
	sort(b + 1, b + 1 + n);
	int size = unique(b + 1, b + 1 + n) - b - 1; // 不重复的个数
	for (int i = 1; i <= n; i++) {
		int x = lower_bound(b + 1, b + 1 + size, a[i]) - b;
		root[i] = update(root[i - 1], 1, size, x);
	}

	while (m--) {
		int x, y, k;
		cin >> x >> y >> k;
		int t = query(root[x - 1], root[y], 1, size, k);
		printf("%d\n", b[t]);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/zhi6fui/article/details/128574206