主席树(可持久化线段树)

模板介绍

基础要求

  • 线段树

  • 能灵活运用 线段树

  • 前缀和,差分

概念

  • 可持久化:在某个历史版本上更改;查询某个历史版本上的值

引入

洛谷P3919 可持久化数组

这道可持久化线段树的题 大概就是单点修改&单点查询

代码大致就是在线段树的基础上改变她存点的方式

以前

tree[k].l == k << 1

tree[k].r == k << 1 | 1

对于可持久化线段树

tree[k].l != k << 1

tree[k].r != k << 1 | 1
inline int tree_build(int k,int l,int r)
{
	k = ++cnt;
    
   每次这样来存点 但l和r仍然保留 表示一个递归和原序列位置的过程
	if(l == r)
	{
		tree[k].val = seq[l];
		return k;
	}
	int mid = l + r >> 1;
	tree[k].l = tree_build(tree[k].l,l,mid);
	tree[k].r = tree_build(tree[k].r,mid + 1,r);
	return k;
}

大概酱紫

#include <map>
#include <cstdio>
#include <iostream>
using namespace std;
#define reg register int
#define isdigit(x) ('0' <= (x)&&(x) <= '9')
template<typename T>
inline T Read(T Type)
{
	T x = 0,f = 1;
	char a = getchar();
	while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();}
	while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();}
	return x * f;
}
const int MAXN = 1e6 + 10;
int seq[MAXN],root[MAXN],cnt,tot;
struct node
{
	int l,r,val;
}tree[MAXN * 13];
inline int tree_build(int k,int l,int r)
{
	k = ++cnt;
	if(l == r)
	{
		tree[k].val = seq[l];
		return k;
	}
	int mid = l + r >> 1;
	tree[k].l = tree_build(tree[k].l,l,mid);
	tree[k].r = tree_build(tree[k].r,mid + 1,r);
	return k;
}
inline int update(int k,int l,int r,int pos,int v)
{
	tree[++cnt] = tree[k];
	k = cnt;
	if(l == r)
	{
		tree[k].val = v;
		return k;
	}
	int mid = l + r >> 1;
	if(pos <= mid) tree[k].l = update(tree[k].l,l,mid,pos,v);
	else tree[k].r = update(tree[k].r,mid + 1,r,pos,v);
	return k;
}
inline int query(int k,int pos,int l,int r)
{
	if(l == r) return tree[k].val;
	int mid = l + r >> 1;
	if(pos <= mid) return query(tree[k].l,pos,l,mid);
	return query(tree[k].r,pos,mid + 1,r);
}
int main()
{
	int n = Read(1),m = Read(1);
	for(reg i = 1;i <= n;i++)
		seq[i] = Read(1);
	root[tot] = tree_build(1,1,n);
	while(m--)
	{
		int v = Read(1),sit = Read(1),loc = Read(1);
		if(sit & 1)
		{
			int value = Read(1);
			root[++tot] = update(root[v],1,n,loc,value);
		} else {
			int ans = query(root[v],loc,1,n);
			root[++tot] = root[v];
			printf("%d
",ans);
		}
	}
    return 0;
}

真正的板子题

针对会主席树的同学

看看我写的思路 应该就可以秒懂了

P3834 可持久化线段树 1

建权值线段树

如图 每个点的权值为([a,b])的数值个数

样例输入

5 5
25957 6405 15770 26287 26465 
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1

数据很大 (−(10^9)(a_i)(10^9))想到离散化

int len = unique(seq + 1,seq + 1 + n) - seq - 1;
	for(reg i = 1;i <= n;i++)
		int it = lower_bound(seq + 1,seq + 1 + len,past_a[i]) - seq;

(Firstly)

查询思路

很容易理解 对于一个询问([a,b])

我们先求([1,b])中有多少个数值

再求([1,a - 1])中的数值个数

每一个节点的权值对应相减

得到的就是([a,b])中的数值个数

因为我们建立的是权值线段树

所以一个节点(*p)

如果(k)(第(k)小值)≤ (*p->val)

向左子树查找

反之向右

直到(l == r)

找到了第(k)小值(hash)(离散化)后的值

离散化时 用一个下标(离散化)后的值对应 原值的数组

就可以输出了

(Secondly)

查询要点

我们先求([1,b])中有多少个数值

再求([1,a - 1])中的数值个数

每一个节点的权值对应相减

这个操作明显复杂且难以实现 时间复杂度也不能保证

我们便考虑每次(query)时传两个(同时做两个线段树的操作)

inline int query(int a,int b,int k,int l,int r)
{
	if(l == r) return l;
	int x = tree[tree[a].l].val - tree[tree[b].l].val;
	int mid = l + r >> 1;
	if(k <= x) return query(tree[a].l,tree[b].l,k,l,mid);
	return query(tree[a].r,tree[b].r,k - x,mid + 1,r);
}

(Thirdly)

修改

最开始是空树

每次添点时把她当做一种新的历史状态

直接用主席树维护

(Code)

#include <map>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define reg register int
#define isdigit(x) ('0' <= (x)&&(x) <= '9')
template<typename T>
inline T Read(T Type)
{
	T x = 0,f = 1;
	char a = getchar();
	while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();}
	while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();}
	return x * f;
}
const int MAXN = 2e5 + 10;
int seq[MAXN],root[MAXN << 5],past_a[MAXN],cnt,tot;
struct node
{
	int l,r,val;
}tree[MAXN << 5];
inline int tree_build(int k,int l,int r)
{
	k = ++cnt;
	if(l == r) return k;
	int mid = l + r >> 1;
	tree[k].l = tree_build(tree[k].l,l,mid);
	tree[k].r = tree_build(tree[k].r,mid + 1,r);
	return k;
}
inline int update(int k,int l,int r,int pos)
{
	tree[++cnt] = tree[k];
	k = cnt;
	tree[k].val++;
	if(l == r) return k;
	int mid = l + r >> 1;
	if(pos <= mid) tree[k].l = update(tree[k].l,l,mid,pos);
	else tree[k].r = update(tree[k].r,mid + 1,r,pos);
	return k;
}
inline int query(int a,int b,int k,int l,int r)
{
	if(l == r) return l;
	int x = tree[tree[a].l].val - tree[tree[b].l].val;
	int mid = l + r >> 1;
	if(k <= x) return query(tree[a].l,tree[b].l,k,l,mid);
	return query(tree[a].r,tree[b].r,k - x,mid + 1,r);
}
int main()
{
	int n = Read(1),m = Read(1);
	for(reg i = 1;i <= n;i++)
		past_a[i] = seq[i] = Read(1);
	sort(seq + 1,seq + 1 + n);
	int len = unique(seq + 1,seq + 1 + n) - seq - 1;
	root[tot] = tree_build(1,1,len);
	for(reg i = 1;i <= n;i++)
	{
		int it = lower_bound(seq + 1,seq + 1 + len,past_a[i]) - seq;
		root[i] = update(root[i - 1],1,len,it);
	}
	while(m--)
	{
		int l = Read(1),r = Read(1),k = Read(1);
		int ans = query(root[r],root[l - 1],k,1,len);
		printf("%d
",seq[ans]);
	}
    return 0;
}

习题

(SP11470 TTM - To the moon)

(Code)

#include <map>
#include <cstdio>
#include <iostream>
using namespace std;
#define reg register int
#define isdigit(x) ('0' <= (x)&&(x) <= '9')
template<typename T>
inline T Read(T Type)
{
	T x = 0,f = 1;
	char a = getchar();
	while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();}
	while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();}
	return x * f;
}
const int MAXN = 1e5 + 10;
int seq[MAXN],root[MAXN],cnt,tot;
typedef long long ll;
struct node
{
	int l,r;
	ll val,tag;
}tree[MAXN << 6];
inline int tree_build(int k,int l,int r)
{
	k = ++cnt,tree[k].tag = 0;
	if(l == r) {tree[k].val = seq[l];return k;}
	int mid = l + r >> 1;
	tree[k].l = tree_build(tree[k].l,l,mid);
	tree[k].r = tree_build(tree[k].r,mid + 1,r);
	tree[k].val = tree[tree[k].l].val + tree[tree[k].r].val;
	return k;
}
inline int update(int k,int l,int r,int L,int R,ll v)
{
	tree[++cnt] = tree[k],k = cnt;
	tree[k].val += (R - L + 1) * v;
	if(L == l&&r == R) {tree[k].tag += v;return k;}
	int mid = l + r >> 1;
	if(R <= mid) tree[k].l = update(tree[k].l,l,mid,L,R,v);
	else if(mid < L) tree[k].r = update(tree[k].r,mid + 1,r,L,R,v);
	else tree[k].l = update(tree[k].l,l,mid,L,mid,v),tree[k].r = update(tree[k].r,mid + 1,r,mid + 1,R,v);
	return k;
}
inline ll query(int k,int l,int r,int L,int R,ll tot)
{
	if(l == L&&r == R) return tree[k].val + (r - l + 1) * tot;
	int mid = l + r >> 1;
	if(R <= mid) return query(tree[k].l,l,mid,L,R,tot + tree[k].tag);
	if(mid < L) return query(tree[k].r,mid + 1,r,L,R,tot + tree[k].tag);
	ll k1 = query(tree[k].l,l,mid,L,mid,tot + tree[k].tag),k2 = query(tree[k].r,mid + 1,r,mid + 1,R,tot + tree[k].tag);
	return k1 + k2;
}
int main()
{
	int n = Read(1),m = Read(1),pre = 0;
	for(reg i = 1;i <= n;i++)
		seq[i] = Read(1);
	root[tot] = tree_build(1,1,n);
	while(m--)
	{
		char sit = getchar();
		while(sit != 'C'&&sit != 'H'&&sit != 'B'&&sit != 'Q') sit = getchar();
		if(sit == 'C')
		{
			int l = Read(1),r = Read(1),d = Read(1);
			root[pre + 1] = update(root[pre],1,n,l,r,d * 1ll);
			pre++;
		} 
		if(sit == 'Q')
		{
			int l = Read(1),r = Read(1);
			ll ans = query(root[pre],1,n,l,r,0ll);
			printf("%lld
",ans);
		}
		if(sit == 'H')
		{
			int l = Read(1),r = Read(1),t = Read(1);
			ll ans = query(root[t],1,n,l,r,0ll);
			printf("%lld
",ans);
		}
		if(sit == 'B') pre = Read(1);
	}
    return 0;
}
原文地址:https://www.cnblogs.com/resftlmuttmotw/p/11677261.html