Chairman tree introduction and code template

The full name of the chairman tree is a persistent weighted line segment tree , which is usually used to solve the problem of the k-th smallest interval and the interval mode.

Persistent data structure (Persistent data structure) can always retain each historical version, and supports the immutable characteristics of operations (immutable).

The weighted line segment tree is a line segment tree that records the number of occurrences of each weight.

Why use the chairman tree?

If we require the k-th smallest number in the interval, if we are a little bit more violent, we can open a line segment tree every time we insert it, but the space will definitely burst. Therefore, the chairman tree should be used and inserted on the basis of the original space.

The main idea of ​​the chairman tree :

Save the historical version of each insert operation, so as to query the kth smallest interval.

The basic operation of the chairman tree:

1. Build

2, insert, insert value

3. Query, the kth smallest of the query interval

The basic structure of the chairman tree:

struct Node {
    
    
	int l, r, sum;
} tr[N * 40];

int n, q, m, idx; // idx 为每个数字的下标
int root[N], a[N], b[N]; // root 记录每个数字的根节点,a 为原数组,b 为离散化后的数组

Building operations:

Take l as the left end point and r as the right end point to create a tree

int build(int l, int r)
{
    
    
	int p = ++idx; // 该子树的根节点 
	if (l == r) return p;
	int mid = l + r >> 1;
	tr[p].l = build(l, mid);
	tr[p].r = build(mid + 1, r);
	
	return p;
}

Insert operation:

On the basis of the pre root node, insert x

int insert(int pre, int l, int r, int x)
{
    
    
	int p = ++idx;
	tr[p] = tr[pre];
	if (l == r) {
    
    
		tr[p].sum++;
		return p;
	}
	int mid = l + r >> 1;
	if (x <= mid) 
		tr[p].l = insert(tr[p].l, l, mid, x);
	else
		tr[p].r = insert(tr[p].r, mid + 1, r, x);
	tr[p].sum = tr[tr[p].l].sum + tr[tr[p].r].sum;
	
	return p;
}

Query operation / find the kth smallest interval:

If we require the k-th smallest in the interval [1, r], we only need to find the root node version when r is inserted, and then use the weighted line segment tree.

In the same way, if the kth smallest in the interval [l, r] is required, the idea of ​​prefix sum can be used, and the information in [1, r] can be subtracted from the information in [1, l-1].

Use the difference between q and p to get the information of the left son, and then determine which son the k-th smallest number is in

int query(int p, int q, int l, int r, int k)
{
    
    
	if (l == r) return r;
	int x = tr[tr[q].l].sum - tr[tr[p].l].sum; // 相减得到左儿子的信息 
	int mid = l + r >> 1;
	if (k <= x) // 第 K 小的数在左儿子中 
		return query(tr[p].l, tr[q].l, l, mid, k);
	else // 第 K 小的数在右儿子中 
		return query(tr[p].r, tr[q].r, mid + 1, r, k - x);
}

Basic usage:

1. Find the k-th smallest in the chairperson tree (code with discretization):

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;

struct Node {
    
    
	int l, r, sum;
} tr[N * 40];

int n, q, m, idx;
int root[N], a[N], b[N];

int build(int l, int r)
{
    
    
	int p = ++idx; // 该子树的根节点 
	if (l == r) return p;
	int mid = l + r >> 1;
	tr[p].l = build(l, mid);
	tr[p].r = build(mid + 1, r);
	
	return p;
}

int insert(int pre, int l, int r, int x)
{
    
    
	int p = ++idx;
	tr[p] = tr[pre];
	if (l == r) {
    
    
		tr[p].sum++;
		return p;
	}
	int mid = l + r >> 1;
	if (x <= mid) 
		tr[p].l = insert(tr[p].l, l, mid, x);
	else
		tr[p].r = insert(tr[p].r, mid + 1, r, x);
	tr[p].sum = tr[tr[p].l].sum + tr[tr[p].r].sum;
	
	return p;
}

int query(int p, int q, int l, int r, int k)
{
    
    
	if (l == r) return r;
	int x = tr[tr[q].l].sum - tr[tr[p].l].sum; // 相减得到左儿子的信息 
	int mid = l + r >> 1;
	if (k <= x) // 第 K 小的数在左儿子中 
		return query(tr[p].l, tr[q].l, l, mid, k);
	else // 第 K 小的数在右儿子中 
		return query(tr[p].r, tr[q].r, mid + 1, r, k - x);
}

int main(void)
{
    
    
	scanf("%d%d", &n, &q);
	for (int i = 1; i <= n; i++) {
    
    
		scanf("%d", &a[i]);
		b[i] = a[i];
	}
	sort(b + 1, b + n + 1);
	m = unique(b + 1, b + n + 1) - b - 1;
	
	root[0] = build(1, m);
	for (int i = 1; i <= n; i++) {
    
    
		int t = lower_bound(b + 1, b + m + 1, a[i]) - b;
		root[i] = insert(root[i - 1], 1, m, t);
	}
	
	int l, r, k;
	while (q--) {
    
    
		scanf("%d%d%d", &l, &r, &k);
		int t = query(root[l - 1], root[r], 1, m, k);
		printf("%d\n", b[t]);
	}
	
	return 0;
}

2. The chairman tree finds the interval mode (the number of occurrences in the interval greater than (l-r + 1) / 2):

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;

struct Node {
    
    
	int l, r, sum;
} tr[N * 40];

int n, q, m, idx;
int root[N], a[N], b[N];

int build(int l, int r)
{
    
    
	int p = ++idx; // 该子树的根节点 
	if (l == r) return p;
	int mid = l + r >> 1;
	tr[p].l = build(l, mid);
	tr[p].r = build(mid + 1, r);
	
	return p;
}

int insert(int pre, int l, int r, int x)
{
    
    
	int p = ++idx;
	tr[p] = tr[pre];
	if (l == r) {
    
    
		tr[p].sum++;
		return p;
	}
	int mid = l + r >> 1;
	if (x <= mid) 
		tr[p].l = insert(tr[p].l, l, mid, x);
	else
		tr[p].r = insert(tr[p].r, mid + 1, r, x);
	tr[p].sum = tr[tr[p].l].sum + tr[tr[p].r].sum;
	
	return p;
}

int query(int p, int q, int l, int r, int k)
{
    
    
	if (l == r) return r;
	int x = tr[tr[q].l].sum - tr[tr[p].l].sum; // 相减得到左儿子的信息 
	int y = tr[tr[q].r].sum - tr[tr[p].r].sum; // 右儿子的信息 
	int mid = l + r >> 1;
	if (x > k) // 众数在左儿子中 
		return query(tr[p].l, tr[q].l, l, mid, k);
	else if (y > k) // 众数在右儿子中 
		return query(tr[p].r, tr[q].r, mid + 1, r, k);
	else // 不存在众数
		return 0;
}

int main(void)
{
    
    
	scanf("%d%d", &n, &q);
	for (int i = 1; i <= n; i++) {
    
    
		scanf("%d", &a[i]);
		b[i] = a[i];
	}
	sort(b + 1, b + n + 1);
	m = unique(b + 1, b + n + 1) - b - 1;
	
	root[0] = build(1, m);
	for (int i = 1; i <= n; i++) {
    
    
		int t = lower_bound(b + 1, b + m + 1, a[i]) - b;
		root[i] = insert(root[i - 1], 1, m, t);
	}
	
	int l, r, k;
	while (q--) {
    
    
		scanf("%d%d", &l, &r);
		int t = query(root[l - 1], root[r], 1, m, (r - l + 1) / 2);
		printf("%d\n", b[t]);
	}
	
	return 0;
}

Guess you like

Origin blog.csdn.net/weixin_43772166/article/details/109184508