zoukankan      html  css  js  c++  java
  • [洛谷P3919][题解]可持久化数组&&主席树讲解

    终于发现在哪里启用(Markdown)(LaTeX)了…

    什么是主席树

    主席树的全名是可持久化线段树,从名字就可以看出来,它很持久
    是一种可以回退到任意历史版本的神器!

    如何实现主席树

    主席树的功能看起来很美妙,那我们怎么实现呢?
    既然要记录历史版本,那我们把历史版本都存下来不就完了?
    呵,天真。MLE等着你
    先丢一张图:
    毒瘤的主席树
    看起来很毒瘤对不对,其实我们只要一步步来拆分就好啦~
    首先,这是一棵正常的线段树:
    正常的线段树
    众所周知,线段树在修改或查询时要走过一条长度(nlog n)的路径
    可以发现,只有这条路径上的数被修改了!
    所以我们秉承着不铺张浪费的原则,只需要新建被修改的节点,然后其他节点用之前的
    我们试着修改一下:
    修改了最后一个叶节点的主席树
    这样,我们的空间就小了很多,减少到了(O((n+m)log n))
    具体为什么是这么大自己想一想就好咯
    那么,再修改一个试试?
    在原始版本修改左二节点后变得更加毒瘤的主席树

    代码实现

    主席树有很多版本,所以我们需要用一个rt[N]来记录每个历史版本的根
    而且由于结构的特殊性导致主席树不能用<<1<<1|1来存储子节点,我们必须使用结构体存储

    struct Node {
    	int l,r,data;
    }tr[N*20];//空间的原因刚才已经说过了
    

    那么新建节点也是必须有的了(此处的新建节点需先复制之前的)

    inline int Newnode(int x){
    	tr[++tot]=tr[x];
    	return tot;
    }
    

    建树操作和普通线段树一样:(ls,rs,nmid是我习惯用的宏定义,明白意思就好)

    int Build(int k,int l,int r){
    	k=++tot;
    	if(l==r){//到了
    		tr[k].data=a[l];
    		return tot;
    	}
    	ls=Build(ls,l,nmid);//建左子树
    	rs=Build(rs,nmid+1,r);//建右子树
    	return k;
    }
    

    修改操作要记得新建节点以便后续回退:

    int Modify(int k,int l,int r,int pos,int num){
    	k=Newnode(k);
    	if(l==r){
    		tr[k].data=num;//到了
    	}else {
    		if(pos<=nmid)ls=Modify(ls,l,nmid,pos,num);//往左走
    		else rs=Modify(rs,nmid+1,r,pos,num);//or 往右走
    	}
    	return k;
    }
    

    查询操作也是大同小异:

    int Query(int k,int l,int r,int pos){
    	if(l==r){
    		return tr[k].data;//到了
    	}else {
    		if(pos<=nmid)return Query(ls,l,nmid,pos);//往左走
    		else return Query(rs,nmid+1,r,pos);//or 往右走
    	}
    }
    

    最后查询就直接从对应版本的根上查询就好啦(别忘了询问操作也要复制一遍,此时只新建根就好了)
    Code:(不带注释)

    #include<cstdio>
    #include<cstdlib>
    #include<cmath>
    #include<ctime>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<stack>
    #include<queue>
    #include<vector>
    #include<bitset>
    #include<set>
    #include<map>
    #define LL long long
    #define rg register
    #define us unsigned
    #define eps 1e-6
    #define INF 0x3f3f3f3f
    #define ls tr[k].l
    #define rs tr[k].r
    #define tmid ((tr[k].l+tr[k].r)>>1)
    #define nmid ((l+r)>>1)
    #define Thispoint tr[k].l==tr[k].r
    #define pushup tr[k].wei=tr[ls].wei+tr[rs].wei
    #define pub push_back
    #define lth length
    using namespace std;
    inline void Read(int &x){
    	int f=1;
    	char c=getchar();
    	x=0;
    	while(c<'0'||c>'9'){
    		if(c=='-')f=-1;
    		c=getchar();
    	}
    	while(c>='0'&&c<='9'){
    		x=(x<<3)+(x<<1)+c-'0';
    		c=getchar();
    	}
    	x*=f;
    }
    #define N 1000010
    int n,m,a[N],rt[N],tot;
    struct Node {
    	int l,r,data;
    }tr[N*20];
    inline int Newnode(int x){
    	tr[++tot]=tr[x];
    	return tot;
    }
    int Build(int k,int l,int r){
    	k=++tot;
    	if(l==r){
    		tr[k].data=a[l];
    		return tot;
    	}
    	ls=Build(ls,l,nmid);
    	rs=Build(rs,nmid+1,r);
    	return k;
    }
    int Modify(int k,int l,int r,int pos,int num){
    	k=Newnode(k);
    	if(l==r){
    		tr[k].data=num;
    	}else {
    		if(pos<=nmid)ls=Modify(ls,l,nmid,pos,num);
    		else rs=Modify(rs,nmid+1,r,pos,num);
    	}
    	return k;
    }
    int Query(int k,int l,int r,int pos){
    	if(l==r){
    		return tr[k].data;
    	}else {
    		if(pos<=nmid)return Query(ls,l,nmid,pos);
    		else return Query(rs,nmid+1,r,pos);
    	}
    }
    int main(){
    	Read(n),Read(m);
    	for(rg int i=1;i<=n;i++)Read(a[i]);
    	rt[0]=Build(0,1,n);
    	for(rg int i=1;i<=m;i++){
    		int idx,opt,pos,num;
    		Read(idx),Read(opt),Read(pos);
    		if(opt==1){
    			Read(num);
    			rt[i]=Modify(rt[idx],1,n,pos,num);
    		}else {
    			printf("%d
    ",Query(rt[idx],1,n,pos));
    			rt[i]=rt[idx];
    		}
    	}
    	return 0;
    }
    

    总结

    这个算法还是很好背板子理解的,重点是明白新建节点操作的原因和好处
    完。

    内容来自_ajhfff_的博客(https://www.cnblogs.com/juruoajh/),未经允许,不得转载。
  • 相关阅读:
    正敲着代码,鼠标坏了!
    DB2 OLAP函数的使用(转)
    修剪矩形
    classpath和环境变量设置(转)
    MyEclipse断点调试JavaScript浅析(转)
    Onunload和onbeforeunload方法的异同
    db2中的coalesce函数(转)
    db2:根据TABLEID找table
    [转]DB2行列转换
    DB2删除数据时的小技巧
  • 原文地址:https://www.cnblogs.com/juruoajh/p/12631588.html
Copyright © 2011-2022 走看看