what is 主席树?
主席树这个名字只不过是OIer们在思考政(zhe)治(xue)的时候发明的好(du)听(liu)的名字。其实主席树的大名叫“可持久化线段树”,一听这名字就知道主席树很毒瘤,所以他的发明者叫黄嘉泰(hjt胡锦涛(什么鬼啊?))。
分步理解“可持久化线段树”
主席树的大名“可持久化线段树”。
看“可持久化”这四个字,很好理解,主席树十分持久,因为它可持久化。那什么叫持久呢,“可持久化”定义:可以支持回退,访问之前版本的数据结构;支持回退操作的意思就是可以访问未经过其他操作的版本,也就是说返回到了以前的版本。那么我们继续看“线段树”这几个字眼,十分熟悉!我们可以知道主席树是基于线段树的一种数据结构。
综上所述,主席树是一种持久的,基于线段树的数据结构。
主席树基本原理
前文说了,线段树与主席树的本质是一样的,只不过主席树可持久化,那么难点就在于怎么支持可持久化。
我们想要支持回退操作就可以对每一次修改操作都进行一次复制,将未进行操作的线段树版本进行复制,再对原线段树版本进行修改,那么我们就可以访问到旧版本的线段树了。不过现在问题来了,这样的空间复杂度将会乘上一个m,变成O(n*m)。不用说,肯定会陷入mle中不可自拔。
那我们来分析一下单点修改的线段树:
我们发现只有橙颜色经过的结点才被修改过。那么我们就可以思考,我们可不可以只对这些节点进行修改呢?答案当然是可以的,主席树的基本思想就是只对进行修改的结点进行复制。那么主席树是长什么样子的呢,下面一起来看一下吧。
可以发现这个图中主席树的一些性质:
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;
}