前言:
很早之前写过主席树的模板,但因为没遇到过用主席树的题(做题太少 ),太久没写,就忘了。
于是,今天又学了一遍主席树,有了一点新的理解。
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
的数的个数是 230~100
的个数是 11~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;
}