可持久化线段树

可持久化线段树

可以询问修改过的历史版本

【模板】可持久化线段树 1(可持久化数组)

题目背景

UPDATE : 最后一个点时间空间已经放大

2021.9.18 增添一组 hack 数据 by @panyf

标题即题意

有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集)

题目描述

如题,你需要维护这样的一个长度为 \(N\) 的数组,支持如下几种操作

  1. 在某个历史版本上修改某一个位置上的值

  2. 访问某个历史版本上的某一位置的值

此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)

输入格式

输入的第一行包含两个正整数 \(N, M\), 分别表示数组的长度和操作的个数。

第二行包含\(N\)个整数,依次为初始状态下数组各位的值(依次为 \(a_i\)\(1 \leq i \leq N\))。

接下来\(M\)行每行包含3或4个整数,代表两种操作之一( \(i\) 为基于的历史版本号):

  1. 对于操作1,格式为\(v_i \ 1 \ {loc}_i \ {value}_i\),即为在版本\(v_i\)的基础上,将 \(a_{{loc}_i}\) 修改为 \({value}_i\)

  2. 对于操作2,格式为\(v_i \ 2 \ {loc}_i\),即访问版本\(v_i\)中的 \(a_{{loc}_i}\)的值,生成一样版本的对象应为\(vi\)

输出格式

输出包含若干行,依次为每个操作2的结果。

样例 #1

样例输入 #1

5 10
59 46 14 87 41
0 2 1
0 1 1 14
0 1 1 57
0 1 1 88
4 2 4
0 2 5
0 2 4
4 2 1
2 2 2
1 1 5 91

样例输出 #1

59
87
41
87
88
46

提示

数据规模:

对于30%的数据:\(1 \leq N, M \leq {10}^3\)

对于50%的数据:\(1 \leq N, M \leq {10}^4\)

对于70%的数据:\(1 \leq N, M \leq {10}^5\)

对于100%的数据:\(1 \leq N, M \leq {10}^6, 1 \leq {loc}_i \leq N, 0 \leq v_i < i, -{10}^9 \leq a_i, {value}_i \leq {10}^9\)

经测试,正常常数的可持久化数组可以通过,请各位放心

数据略微凶残,请注意常数不要过大

另,此题I/O量较大,如果实在TLE请注意I/O优化

询问生成的版本是指你访问的那个版本的复制

样例说明:

一共11个版本,编号从0-10,依次为:

0 : 59 46 14 87 41

1 : 59 46 14 87 41

2 : 14 46 14 87 41

3 : 57 46 14 87 41

4 : 88 46 14 87 41

5 : 88 46 14 87 41

6 : 59 46 14 87 41

7 : 59 46 14 87 41

8 : 88 46 14 87 41

9 : 14 46 14 87 41

10 : 59 46 14 87 91


std

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+9;
int n,m,a[N],root[N];
struct tree
{
	int ls,rs,val;
	tree()
	{
		ls = rs = val = 0;
	}
	#define ls(x) t[x].ls
	#define rs(x) t[x].rs
	#define val(x) t[x].val
}t[N*23];
int tot;

int clone(int p)
{
	tot++;
	t[tot] = t[p];//复制信息
	return tot;
}

int build(int p,int l_,int r_)
{
	p = ++tot;
	if(l_ == r_)
	{
		val(p) = a[l_];
		return p;
	}
	
	int mid = (l_+r_)>>1;
	ls(p) = build(ls(p),l_,mid);
	rs(p) = build(rs(p),mid+1,r_);
	return p;记得return
}

int modify(int p,int l_,int r_,int x,int v)
{
	p = clone(p);
	if(l_ == r_)
	{
		val(p) = v;
		return p;
	}
	int mid = (l_+r_)>>1;
	if(x <= mid)ls(p) = modify(ls(p),l_,mid,x,v);
	else rs(p) = modify(rs(p),mid+1,r_,x,v);
	return p;//与普通线段树不同 记得return
}

int query(int p,int l_,int r_,int x)
{
	if(l_ == r_)return val(p);
	int mid = (l_+r_)>>1;
	if(x <= mid)return query(ls(p),l_,mid,x);
	else return query(rs(p),mid+1,r_,x);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++)scanf("%d",&a[i]);
	root[0] = build(0,1,n);
	for(int i = 1;i <= m;i++)
	{
		int vi,op,x,v;
		scanf("%d%d%d",&vi,&op,&x);
		if(op == 1)
		{
			scanf("%d",&v);
			root[i] = modify(root[vi],1,n,x,v);//会产生一个新的版本记得记下来
		}
		else 
		{
			printf("%d\n",query(root[vi],1,n,x));
			root[i] = root[vi];//每次询问也会产生一个新的版本
		}
	}
	
	return 0;
}

可以利用前缀和的思想来求区间第k大的数(可持久化权值线段树)

【模板】可持久化线段树 2

题目背景

这是个非常经典的可持久化权值线段树入门题——静态区间第 \(k\) 小。

数据已经过加强,请使用可持久化权值线段树。同时请注意常数优化

题目描述

如题,给定 \(n\) 个整数构成的序列 \(a\),将对于指定的闭区间 \([l, r]\) 查询其区间内的第 \(k\) 小值。

输入格式

第一行包含两个整数,分别表示序列的长度 \(n\) 和查询的个数 \(m\)
第二行包含 \(n\) 个整数,第 \(i\) 个整数表示序列的第 \(i\) 个元素 \(a_i\)
接下来 \(m\) 行每行包含三个整数 $ l, r, k$ , 表示查询区间 \([l, r]\) 内的第 \(k\) 小值。

输出格式

对于每次询问,输出一行一个整数表示答案。

样例 #1

样例输入 #1

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

样例输出 #1

6405
15770
26287
25957
26287

提示

样例 1 解释

\(n=5\),数列长度为 \(5\),数列从第一项开始依次为\(\{25957, 6405, 15770, 26287, 26465\}\)

  • 第一次查询为 \([2, 2]\) 区间内的第一小值,即为 \(6405\)
  • 第二次查询为 \([3, 4]\) 区间内的第一小值,即为 \(15770\)
  • 第三次查询为 \([4, 5]\) 区间内的第一小值,即为 \(26287\)
  • 第四次查询为 \([1, 2]\) 区间内的第二小值,即为 \(25957\)
  • 第五次查询为 \([4, 4]\) 区间内的第一小值,即为 \(26287\)

数据规模与约定

  • 对于 \(20\%\) 的数据,满足 \(1 \leq n,m \leq 10\)
  • 对于 \(50\%\) 的数据,满足 \(1 \leq n,m \leq 10^3\)
  • 对于 \(80\%\) 的数据,满足 \(1 \leq n,m \leq 10^5\)
  • 对于 \(100\%\) 的数据,满足 \(1 \leq n,m \leq 2\times 10^5\)\(|a_i| \leq 10^9\)\(1 \leq l \leq r \leq n\)\(1 \leq k \leq r - l + 1\)

std

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+9;
int n,m,a[N],b[N];
map<int,int>book;
int root[N],tot;
struct tree
{
	int ls,rs,sum;
	tree()
	{
		ls = rs = sum = 0;
	}
	#define ls(x) t[x].ls
	#define rs(x) t[x].rs
	#define sum(x) t[x].sum
}t[N*23];
void iscre()
{
	sort(b+1,b+1+n);
	int k = unique(b+1,b+1+n)-b-1;
	for(int i = 1;i <= n;i++)
	{
		int t = lower_bound(b+1,b+1+k,a[i])-b;
		book[t] = a[i];
		a[i] = t;
	}
}

void pushup(int p)
{
	sum(p) = sum(ls(p)) + sum(rs(p));
}

int clone(int p)
{
	t[++tot] = t[p];
	return tot;
}

int build(int p,int l_,int r_)
{
	p = ++tot;
	if(l_ == r_)return p;
	
	int mid = (l_+r_)>>1;
	ls(p) = build(ls(p),l_,mid);
	rs(p) = build(rs(p),mid+1,r_);
	//pushup(p);
	return p;
}

int modify(int p,int l_,int r_,int x)
{
	p = clone(p);
	if(l_ == r_)
	{
		sum(p)++;
		return p;
	}
	
	int mid = (l_+r_)>>1;
	if(x <= mid)ls(p) = modify(ls(p),l_,mid,x);
	else rs(p) = modify(rs(p),mid+1,r_,x);
	pushup(p);
	return p;
}

int query(int lp,int rp,int l_,int r_,int k)
{
	if(l_ == r_)return book[l_];
	
	int x = sum(ls(rp))-sum(ls(lp));
	int mid = (l_+r_)>>1;
	if(k <= x)return query(ls(lp),ls(rp),l_,mid,k);
	else return query(rs(lp),rs(rp),mid+1,r_,k-x);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++)scanf("%d",&a[i]),b[i] = a[i];
	iscre();

	root[0] = build(0,1,n);
	for(int i = 1;i <= n;i++)root[i] = modify(root[i-1],1,n,a[i]);
	
	while(m--)
	{
		int l,r,k;
		scanf("%d%d%d",&l,&r,&k);
		printf("%d\n",query(root[l-1],root[r],1,n,k));
	}
	
	return 0;
}
posted @ 2022-11-02 21:38  AC7  阅读(39)  评论(0)    收藏  举报