主席树的一些理解

前言:

很早之前写过主席树的模板,但因为没遇到过用主席树的题(做题太少 ),太久没写,就忘了。

于是,今天又学了一遍主席树,有了一点新的理解。


1,离散化。

	for (int i = 1; i <= n;i++)
	{
		cin >> v[i];
		pos.emplace_back(v[i]);
	}
	sort(pos.begin(), pos.end());
	pos.erase(unique(pos.begin(), pos.end()), pos.end());

主席树的节点储存的是l~r 范围内的数的个数。
举个例子:
1 22 33 444
那么1~30 的数的个数是 2
30~100的个数是 1
1~500的个数是4

因为大多数情况题目给出的s[i] 的范围是0~1e9 如果用这个范围去建树,则空间复杂度爆炸。


2,建树过程

	function<void(int,int,int,int&,int)> build=[&](int l, int r, int pre, int &now, int t) {
		tre[++cnt] = tre[pre];//新建一个节点,并将前一个节点复制给它,相当于传递。
		now = cnt;//更改当前节点编号。
		tre[now].sum++;
		if (l == r)return;
		int mid = (l + r) >> 1;
		if (t <= mid)build(l, mid, tre[pre].l, tre[now].l, t);
		else build(mid + 1, r, tre[pre].r, tre[now].r, t);
	};

每插入一个点就要新建一个树。
这里用root[]记录每个起始点。

建树是写主席树的最关键的部分,理解了建树的过程,代码如有神。
每次build() 都是建树的过程。

每次加入一个数,其造成的印象是一条链,即:一个数只会对当前树的某一条链造成影响。

如下图:新增了一个节点,这个节点相当于完全复制了前一个节点的全部信息,又因为这个新加入的数影响的是一条链。所以要进入子节点,并进行修改。
主席树的一些理解
进入子节点如下图:
和上一个结点一样,新增一个节点,该节点完全复制原来的节点,然后进行修改,如此反复,就可以做到新增一条链,也就是新建一棵树了。
主席树的一些理解
4,查询

	function<int(int,int,int,int,int)>query = [&](int l, int r, int L, int R, int k){
		if (l == r)return l;
		int mid = (l + r) >> 1;
		int sum = tre[tre[R].l].sum - tre[tre[L].l].sum;
		if (k <= sum)return query(l, mid, tre[L].l, tre[R].l, k);
		else return query(mid + 1, r, tre[L].r, tre[R].r, k - sum);
	};

查询操作容易错的是:

int sum = tre[tre[R].l].sum - tre[tre[L].l].sum;

查询的是两个状态的左子节点的和。

5,完整代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
struct AC
{
	int l, r, sum;
}tre[MAXN<<5];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n, m;int cnt = 0;
	cin >> n >> m;
	vector<int >root(n + 1);
	vector<int >v(n + 1);
	vector<int>pos;
	for (int i = 1; i <= n;i++)
	{
		cin >> v[i];
		pos.emplace_back(v[i]);
	}
	sort(pos.begin(), pos.end());
	pos.erase(unique(pos.begin(), pos.end()), pos.end());
	function<void(int,int,int,int&,int)> build=[&](int l, int r, int pre, int &now, int t) {
		tre[++cnt] = tre[pre];
		now = cnt;
		tre[now].sum++;
		if (l == r)return;
		int mid = (l + r) >> 1;
		if (t <= mid)build(l, mid, tre[pre].l, tre[now].l, t);
		else build(mid + 1, r, tre[pre].r, tre[now].r, t);
	};
	function<int(int,int,int,int,int)>query = [&](int l, int r, int L, int R, int k){
		if (l == r)return l;
		int mid = (l + r) >> 1;
		int sum = tre[tre[R].l].sum - tre[tre[L].l].sum;
		if (k <= sum)return query(l, mid, tre[L].l, tre[R].l, k);
		else return query(mid + 1, r, tre[L].r, tre[R].r, k - sum);
	};
	auto getpos = [&](int t){
		return lower_bound(pos.begin(), pos.end(), t) - pos.begin() + 1;
	};

	for (int i = 1; i <= n; i++)build(1,n,root[i-1],root[i],getpos(v[i]));
	while (m--)
	{
		int l, r, k;
		cin >> l >> r >> k;
		cout << pos[query(1, n, root[l - 1], root[r], k) - 1] << endl;
	}
	return 0;
}
上一篇:8.14考试总结(NOIP模拟40)[打地鼠·竞赛图·糖果·树]


下一篇:bzoj4919:大根堆