【学习笔记】可持久化数据结构学习笔记

主席树:

可持久化权值线段树也被称为主席树,它通过建新节点来维护历史值,使得能在短时间内查找历史值。

模板题题目链接

讲解:

方法一:

每次二分答案 \(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;
} 
posted @ 2020-09-10 21:36  Jayun  阅读(75)  评论(0编辑  收藏  举报