可持久化数组(可持久化线段树/平衡树/主席树)

题目链接

 

 

 

what is 主席树?

      主席树这个名字只不过是OIer们在思考政(zhe)治(xue)的时候发明的好(du)听(liu)的名字。其实主席树的大名叫“可持久化线段树”,一听这名字就知道主席树很毒瘤,所以他的发明者叫黄嘉泰(hjt胡锦涛(什么鬼啊?))。

分步理解“可持久化线段树”

       主席树的大名“可持久化线段树”。

       看“可持久化”这四个字,很好理解,主席树十分持久,因为它可持久化。那什么叫持久呢,“可持久化”定义:可以支持回退,访问之前版本的数据结构;支持回退操作的意思就是可以访问未经过其他操作的版本,也就是说返回到了以前的版本。那么我们继续看“线段树”这几个字眼,十分熟悉!我们可以知道主席树是基于线段树的一种数据结构

       综上所述,主席树是一种持久的,基于线段树的数据结构


主席树基本原理

       前文说了,线段树与主席树的本质是一样的,只不过主席树可持久化,那么难点就在于怎么支持可持久化。

       我们想要支持回退操作就可以对每一次修改操作都进行一次复制,将未进行操作的线段树版本进行复制,再对原线段树版本进行修改,那么我们就可以访问到旧版本的线段树了。不过现在问题来了,这样的空间复杂度将会乘上一个m,变成O(n*m)。不用说,肯定会陷入mle中不可自拔。

      那我们来分析一下单点修改的线段树:主席树1

       我们发现只有橙颜色经过的结点才被修改过。那么我们就可以思考,我们可不可以只对这些节点进行修改呢?答案当然是可以的,主席树的基本思想就是只对进行修改的结点进行复制。那么主席树是长什么样子的呢,下面一起来看一下吧。主席树2

可以发现这个图中主席树的一些性质:

1、每一次修改增加的节点个数为log(n)。

2、增加的非叶子结点会连向一个是其他版本的节点,一个是连向新节点。

3、主席树有很多根……

4、对于每一个根都可以构成一棵完整的线段树。

5、每一个节点都有可能有不只一个爸爸……

     所以我们可以知道主席树只会对部分节点进行复制,并且每一次复制的节点个数是log(n)。我们每一次想询问一个版本的线段树,就可以在那个版本的根构成的线段树中询问。

但同时也延伸出许多问题:

1、怎么构建新节点?怎么给新节点编号?怎么连边?

2、怎么访问子节点?

3、怎么存根?

     很明显这些问题在线段树中完全不会出现,我们可以感觉到主席树在建树的代码中会和线段树不同。

现在给出刚才问题的答案:

1、直接开一块内存池存新节点。编号为此时总节点数个数+1。开结构体存子节点编号;线段树建什么边,一指了事。

2、访问子节点编号,不是像线段树一样乘2或乘2+1,而是在结构体存子节点编号。

3、另外开个数组存。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
struct node
{
	int l,r,v;
}tree[N*20];//线段树 
int root[N];//存每棵线段树的根节点 
int cnt;//节点数 
/*
思考:
其实主席树就是从线段树发展过来的
线段树每个节点存的最大/最小/和...值
一般线段树用一个数组去表示 1到n 比如线段树tree[1] 代表的是区间1-n的最大值或者最小值..
线段树去更新 查询都是定义k(节点下标) l,r(取值范围1-n)这样操作

主席树是定义了一个 node 类型的数组去操作 有左右 所以主席树的二叉树的下标不是如1.2.3.4.5....这样
 
*/ 
void build(int &now,int l,int r)
{
	now=++cnt;//每次都申请一个结点 
	if(l==r){
		scanf("%d",&tree[now].v);
		return ;
	}
	int mid=(l+r)>>1;
	build(tree[now].l,l,mid);//先从右边建树所以靠右边是1.2.3.... 这里左是把tree[now].l传进来了 
	build(tree[now].r,mid+1,r);
}
//主席树的修改与查询和线段树差不多 都是 1-n 每次除2 这样
//有点区别是主席树是去左边是tree[now].l 
void update(int &now,int last,int l,int r,int pos,int val)
{
	now=++cnt; 
	tree[now]=tree[last];
	if(l==r){
		tree[now].v=val;
		return;
	}
	int mid=(l+r)>>1;
	//这里的tree[now].l tree[last].l 注意下 
	if(pos<=mid) update(tree[now].l,tree[last].l,l,mid,pos,val);
	else update(tree[now].r,tree[last].r,mid+1,r,pos,val);
}
int query(int now,int l,int r,int pos)
{
	if(l==r){
		return tree[now].v;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) return query(tree[now].l,l,mid,pos);
	else return query(tree[now].r,mid+1,r,pos);
}
int main()
{
	int n,m;
	while(scanf("%d %d",&n,&m)==2){
		memset(root,0,sizeof(root));
		cnt=0;
		build(root[0],1,n);
		for(int i=1;i<=m;i++){
			int a,b,c;
			scanf("%d %d %d",&a,&b,&c);
			if(b==1){
				int d;
				scanf("%d",&d);
				update(root[i],root[a],1,n,c,d);
				
			}
			else{
				printf("%d\n",query(root[a],1,n,c));
				root[i]=root[a];
			}
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/CC_1012/article/details/89163207
今日推荐