主席树

又名函数式线段树。(这不重要)

一个数据结构,能访问到历史版本的数据,常用于可持久化和区间K大值,是线段树的一个升级版。专门用于区间第K大值查询问题。

主席树是可持久化线段树的一种特殊实现。

原理

首先我们要将所有数字离散化。

主席树相当于是在每个位置维护了一个线段树,线段树的节点是一个区间[x, y],这里的 x 和 y 都是离散后数字的编号。

主席树节点中维护的值,是[1, i]这个区间有数字出现的次数(这个说法初看可能不能理解,举个例子就很好懂了)。当我们查询时,就利用到了前缀和的思想。

那么,来举例吧。

7
1 5 2 6 3 7 4     //离散化后的数组

首先建树,

然后逐一插入数字。将每个数字的编号插入到相应位置上,并且把所有经过的区间的值都加1。

插入1,

插入5,

插入2,

直到全部插入完成,树是这样的。

查询的原理也很好理解。

例如要查询[2, 5]中第3大的数。

我们先把第一棵线段树和第五棵6线段树拿出来。

可以发现,将相应节点的数相减,刚刚好就是[2, 5]内某个范围内的数的个数。比如[1, 4]这个节点相减是2,就说明[2, 5]内有两个数是在1~4范围内(即2和3)。

所以对于一个区间[l, r],我们可以每次算出在[l, mid]范围内的数,如果数量>=K(K就是第K大),就往左子树走,否则就往右子树走。

int query(int ll, int rr, int l, int r, int k) {
	int x = st[st[rr].l].val - st[st[ll].l].val;
	if (l == r) return b[l];
	int mid = l + r >> 1;
	if (x >= k) return query(st[ll].l, st[rr].l, l, mid, k);
	return query(st[ll].r, st[rr].r, mid + 1, r, k - x);
}

直接返回第K大的数值,而不是离散化后相应的编号。

题目

P3834 【模板】可持久化线段树 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

经典可持久化权值线段树入门题——静态区间第K小。

代码实现

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
// fixed << setprecision(6)
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int MOD = 998244353;
const int N = 2e5 + 5;
int n, q, m, cnt;
int a[N], b[N], rt[N];
struct node {
	int l, r, val;
}st[N << 5];
void bt(int &k, int l, int r) {
	k = ++cnt;
	if (l == r) return ;
	int mid = l + r >> 1;
	bt(st[k].l, l, mid);
	bt(st[k].r, mid + 1, r);
}
void change(int &k, int kk, int l, int r, int ll) {
	k = ++cnt;
	st[k] = st[kk];
	++st[k].val;
	if (l == r) return ;
	int mid = l + r >> 1;
	if (ll <= mid)
		change(st[k].l, st[kk].l, l, mid, ll);
	else
		change(st[k].r, st[kk].r, mid + 1, r, ll);
}
int query(int ll, int rr, int l, int r, int k) {
	int x = st[st[rr].l].val - st[st[ll].l].val;
	if (l == r) return b[l];
	int mid = l + r >> 1;
	if (x >= k) return query(st[ll].l, st[rr].l, l, mid, k);
	return query(st[ll].r, st[rr].r, mid + 1, r, k - x);
}
void solve() {
    cin >> n >> q;
    for (int i = 1; i <= n; ++i) {
    	cin >> a[i];
    	b[i] = a[i];
	}
	sort(b + 1, b + 1 + n);
	int len = unique(b + 1, b + 1 + n) - b - 1;
	bt(rt[0], 1, len);
	for (int i = 1; i <= n; ++i) {
		int t = lower_bound(b + 1, b + 1 + len, a[i]) - b;
		change(rt[i], rt[i - 1], 1, len, t);
	}
	while (q--) {
		int l, r, k;
		cin >> l >> r >> k;
		cout << query(rt[l - 1], rt[r], 1, len, k) << endl;
	}
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    int _ = 1;
    // cin >> _;
    while (_--) solve();
    return 0;
}

参考链接:【Notes】【主席树】hdu2665 Kth number_划分树支持修改吗-CSDN博客

posted @ 2024-10-10 19:34  clockleaf  阅读(34)  评论(0)    收藏  举报