主席树求静态区间第k小

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

题意

题目链接

给定一个序列,不对其进行任何修改操作。进行 m m m次询问,每次给定两个数 l , r ( 1 ≤ l ≤ r ≤ n ≤ 2 × 1 0 5 ) l,r(1\leq l \leq r \leq n \leq 2\times10^5) l,r(1lrn2×105),问在区间 [ l , r ] [l,r] [l,r]内的第 k k k小的数是多少。

做法

现在假设题目只有一次查询,假设询问 [ 1 , r ] [1,r] [1,r]内的第 k k k小,此时我们可以采用权值线段树存储 [ 1 , r ] [1,r] [1,r]序列。对于当前节点,如果想要查询的 k k k小于左子树的大小,那么第 k k k个数一定落在左子树上,否则一定落在右子树上。当落在左子树上时,直接递归查询左子树里第 k k k小;当落在右子树上时,设左子树大小为 s s s,直接递归查询右子树里第 k − s k-s ks小。

现在扩展问题,如果有 m m m次询问,第 i i i次询问区间 [ 1 , r i ] [1,r_i] [1,ri]内的第 k k k小。我们如果直接按照刚刚的方式每遇到一个 r i r_i ri就建立一棵权值线段树,空间是吃不消的。这个时候,就要用上主席树的思想了。我们发现,如果我们从 1 1 1 n n n都建立一个 [ 1 , r ] [1,r] [1,r]的权值线段树,那么相邻两个权值线段树中的权值,只有一个权值不同,且权值变化值只有 1 1 1。也就是说,两棵相邻的权值线段树中间会有非常多的相同的节点,存储相同节点意味着空间的浪费。所以采用主席树的思想,我们从 [ 1 , r ] [1,r] [1,r]的权值线段树扩展到 [ 1 , r + 1 ] [1,r+1] [1,r+1]时,我们只关注变化的那个数值,而且只有从根节点到那一个权值变化的点的那条路径有变化。所以建立 [ 1 , r + 1 ] [1,r+1] [1,r+1]时只需要多开 log ⁡ n \log n logn个节点。这样针对每一个 [ 1 , r i ] [1,r_i] [1,ri],只要我们找到对应的 r i r_i ri的根节点就可以完成查询。

现在再扩展问题,把询问变成 [ l , r ] [l,r] [l,r]。因为我们权值线段树的建立是按照相同数字的数量,所以可以采取前缀和的思想,通过两个权值线段树 [ 1 , l − 1 ] [1,l-1] [1,l1] [ 1 , r ] [1,r] [1,r]求差值求出区间的权值线段树,通过最开始的思路就可以求出静态区间 [ l , r ] [l,r] [l,r]的第 k k k小了。

数据范围很大,建立权值线段树的时候需要先进行离散化。

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

int n, m, mi, tot;
int pre[200050], hsh[200050];

struct num {
	int id, x, mp;
}a[200050];

struct Tree {
	int l, r, cnt, ls, rs;
}t[6000050];

void pu(int ni) {
	t[ni].cnt = t[t[ni].ls].cnt + t[t[ni].rs].cnt;
}

int build_tree(int l, int r) {
	int ni = ++tot;
	t[ni].l = l; t[ni].r = r; t[ni].cnt = 0;
	if (l == r) {
		t[ni].ls = t[ni].rs = -1;
		return ni;
	}
	int mid = (l + r) >> 1;
	t[ni].ls = build_tree(l, mid);
	t[ni].rs = build_tree(mid + 1, r);
	pu(ni); 
	return ni;
} 

int fix(int li, int l, int r, int pos, int x) {
	int ni = ++tot;
	t[ni].l = l; t[ni].r = r;
	if (l == r) {
		t[ni].ls = t[ni].rs = -1;
		t[ni].cnt = t[li].cnt + x;
		return ni;
	}
	int mid = (l + r) >> 1;
	if (pos <= mid) {
		t[ni].rs = t[li].rs;
		t[ni].ls = fix(t[li].ls, l, mid, pos, x);
	}
	else {
		t[ni].ls = t[li].ls;
		t[ni].rs = fix(t[li].rs, mid + 1, r, pos, x);
	}
	pu(ni);
	return ni;
}

int query(int li, int ri, int l, int r, int k) {
	if (l >= r) {
		return hsh[l];
	}
	int mid = (l + r) >> 1;
	int delta = t[t[ri].ls].cnt - t[t[li].ls].cnt;
	if (k <= delta) {
		return query(t[li].ls, t[ri].ls, l, mid, k);
	}
	else return query(t[li].rs, t[ri].rs, mid + 1, r, k - delta);
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m; 
	mi = tot = 0;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i].x;
		a[i].id = i;
	}
	sort(a + 1, a + n + 1, [](const num &A, const num &B) {
		return A.x < B.x;
	});
	a[1].mp = 1; 
	hsh[a[1].mp] = a[1].x;
	for (int i = 2; i <= n; ++i) {
		if (a[i].x == a[i - 1].x) a[i].mp = a[i - 1].mp;
		else a[i].mp = a[i - 1].mp + 1;
		hsh[a[i].mp] = a[i].x;
	}
	mi = a[n].mp;
	sort(a + 1, a + n + 1, [](const num &A, const num &B) {
		return A.id < B.id;
	});
	pre[0] = build_tree(1, mi);
	for (int i = 1; i <= n; ++i) {
		pre[i] = fix(pre[i - 1], 1, mi, a[i].mp, 1);
	}
	for (int i = 1; i <= m; ++i) {
		int x, y, k;
		cin >> x >> y >> k;
		cout << query(pre[x - 1], pre[y], 1, mi, k) << "\n";
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值