【学习笔记】可持久化数据结构学习笔记
主席树:
可持久化权值线段树也被称为主席树,它通过建新节点来维护历史值,使得能在短时间内查找历史值。
讲解:
方法一:
每次二分答案 \(x\),然后求出区间内多少个小于等于 \(x\) 的数。而考虑怎么求,我们可以用权值线段树。把所有数离散化,维护 \(n\) 棵权值线段树,第 \(i\) 棵表示数列前 \(i\) 个数。它每个节点里的数就是数值在该区间范围内的个数。
查询 \([l,r]\) 中小于等于 \(x\) 的数的个数,相当于分别查询第 \(r\) 课数权值线段树的 \([1,x]\) 个数和第 \((l-1)\) 棵权值线段树的 \([1,x]\) 个数。相减即可得到区间小于等于 \(x\) 的数。
然后你会发现,其实二分的过程在权值线段树里就已经进行过了,可以忽略了。最终时间复杂度 \(\mathcal{O}(m\log n)\),空间 \(\mathcal{O}(n^2)\)。
方法二:
继续优化。空间太大,我们其实不用建出 \(n\) 棵完整线段树。因为第 \(i\) 棵和第 \(i-1\) 棵只有一部分不一样,那么一样的部分直接继承 \(i-1\) 的节点。
代码:
const int N = 200010;
struct Seg
{
int lt[N<<5], rt[N<<5], cnt;
ll sum[N<<5];
void build(int &x, int l, int r)
{
x = ++cnt;
if(l == r) return;
int mid = (l + r) >> 1;
build(lt[x], l, mid);
build(rt[x], mid + 1, r);
}
int change(int x, int l, int r, int p)
{
int Newx = ++cnt; lt[Newx] = lt[x], rt[Newx] = rt[x], sum[Newx] = sum[x] + 1;
if (l == r) return Newx;
int mid = (l + r) >> 1;
if(p <= mid) lt[Newx] = change(lt[Newx], l, mid, p);
else rt[Newx] = change (rt[Newx], mid + 1, r, p);
return Newx;
}
int query (int L, int R, int l, int r, int p)
{
int ans, mid = (l + r) >> 1, x = sum[lt[R]] - sum[lt[L]];
if(l == r) return l;
if(p <= x) ans = query (lt[L], lt[R], l, mid, p);
else ans = query (rt[L], rt[R], mid + 1, r, p - x);
return ans;
}
}sgt;
int n, m;
ll a[N], b[N]; int root[N];
int main()
{
scanf ("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf ("%d", &a[i]), b[i] = a[i];
sort(b + 1, b + 1 + n);
int nn = unique(b + 1, b + 1 + n) - b - 1;
sgt.build(root[0], 1, nn);
for (int i = 1; i <= n; i++)
{
int p = lower_bound(b + 1, b + 1 + nn, a[i]) - b;
root[i] = sgt.change(root[i - 1], 1, nn, p);
}
for (int i = 1; i <= m; i++)
{
int l, r, k;
scanf ("%d%d%d", &l, &r, &k);
int ans = sgt.query(root[l - 1], root[r], 1, nn, k);
printf("%lld\n", b[ans]);
}
return 0;
}