zoukankan      html  css  js  c++  java
  • 主席树入门详解+题目推荐

    主席树学名可持久化线段树,就是这个可持久化,衍生了多少数据结构

    为什么会有主席树这个数据结构呢?它被发明是用来解决什么问题的呢?

    给定n个数,m个操作,操作类型有在某个历史版本下单点修改,输出某个历史版本下某个位置的值的值,n和m小于等于1e6

    乍一看是不是一点头绪也没有。我们先来想想暴力怎么做,暴力存储第i个状态下每个数的值,显然这样做不是TLE就是MLE,我们不妨管这种状态叫做TM双LE。

    如果没有这个历史状态显然处理很简单,一个线段树就解决了。那么加上历史状态呢?如果我们优化一下暴力,我们会发现我们可以建若干棵树,一棵树存储一个状态下的所有信息。

    显然这种处理方式还不如刚才呢,状态的转移依然很慢,MLE也更加严重了,所以我们还是TM双LE。怎么办呢?我们要想办法加快转移,同时优化空间,两者要同时做到似乎有点难,这个时候就要用到主席树了。

    主席树是怎么维持可持久化的呢?跟上面说的一样建若干棵树,第i棵树表示第i次操作后的状态。我们会发现,在每次修改时,两个子节点中只有一个会被修改,也就是说一次修改只会有logn个节点被修改,那么显然所有节点都新建备份是又慢又浪费的。我们可以让修改后的树跟修改前的树共享节点,大大节省了时间和空间,这道题就做完了。

    这是题面

    那么直接上代码吧

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cctype>
    #define ll long long
    #define gc getchar
    #define maxn 1000005
    using namespace std;
    
    inline ll read(){
    	ll a=0;int f=0;char p=gc();
    	while(!isdigit(p)){f|=p=='-';p=gc();}
    	while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
    	return f?-a:a;
    }int n,m,a[maxn];
    
    struct ahaha{
    	int v,ch[2];
    }t[maxn*20];int cnt,num,rt[maxn];
    #define lc t[i].ch[0]
    #define rc t[i].ch[1]
    #define Lc t[j].ch[0]
    #define Rc t[j].ch[1]
    void build(int &i,int l,int r){
    	i=++num;
    	if(l==r){t[i].v=a[l];return;}
    	int m=l+r>>1;
    	build(lc,l,m);build(rc,m+1,r);
    }
    void update(int &i,int j,int l,int r,int k,int z){
    	i=++num;lc=Lc;rc=Rc;  //共用一个子节点节省空间,加快速度
    	if(l==r){t[i].v=z;return;}
    	int m=l+r>>1;
    	if(k<=m)update(lc,Lc,l,m,k,z);
    	else update(rc,Rc,m+1,r,k,z);
    }
    int query(int i,int l,int r,int k){
    	if(l==r)return t[i].v;
    	int m=l+r>>1;
    	if(k<=m)return query(lc,l,m,k);
    	return query(rc,m+1,r,k);
    }
    
    inline void solve_1(int k){
    	int x=read(),z=read();
    	update(rt[++cnt],rt[k],1,n,x,z);
    }
    inline void solve_2(int k){
    	int x=read();rt[++cnt]=rt[k];
    	printf("%d
    ",query(rt[cnt],1,n,x));
    }
    
    int main(){
    	n=read();m=read();
    	for(int i=1;i<=n;++i)
    		a[i]=read();
    	build(rt[0],1,n);  //先把第0版本的树建出来
    	while(m--){
    		int k=read(),zz=read();
    		switch(zz){
    			case 1:solve_1(k);break;
    			case 2:solve_2(k);break;
    		}
    	}
    	return 0;
    }
    

    提到主席树,想必各位最先想到的还是区间第k大

    区间第k大是怎么利用可持久化的呢?

    首先说一下什么是权值线段树。平常的线段树下标是表示第几个数,权值线段树的下标是代表数字的值,那么节点的权值就是代表数字出现的次数。

    那么维护区间第k大就需要建n棵权值线段树,第i棵树维护的是区间([1,i])中每个数出现的次数

    很显然用刚才的方法维护就ok了

    上代码

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cctype>
    #define ll long long
    #define gc getchar
    #define maxn 200005
    using namespace std;
    
    inline ll read(){
    	ll a=0;int f=0;char p=gc();
    	while(!isdigit(p)){f|=p=='-';p=gc();}
    	while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
    	return f?-a:a;
    }int n,m,cnt,a[maxn],b[maxn];
    
    struct ahaha{
    	int v,ch[2];
    }t[maxn*20];int num,rt[maxn];
    #define lc t[i].ch[0]
    #define rc t[i].ch[1]
    #define Lc t[j].ch[0]
    #define Rc t[j].ch[1]
    void update(int &i,int j,int l,int r,int k){
    	i=++num;t[i]=t[j];++t[i].v;
    	if(l==r)return;
    	int m=l+r>>1;
    	if(k<=m)update(lc,Lc,l,m,k);
    	else update(rc,Rc,m+1,r,k);
    }
    int query(int i,int j,int l,int r,int k){
    	if(l==r)return l;
    	int m=l+r>>1,v=t[Lc].v-t[lc].v;
    	if(k<=v)return query(lc,Lc,l,m,k);
    	return query(rc,Rc,m+1,r,k-v);
    }
    
    inline void solve(){
    	int x=read(),y=read(),k=read();
    	printf("%d
    ",b[query(rt[x-1],rt[y],1,cnt,k)]);   //别忘了要求输出的是原数,别把离散化后的值输出了
    }
    
    int main(){
    	n=read();m=read();
    	for(int i=1;i<=n;++i)  //先要离散化,否则没法存
    		a[i]=b[i]=read();
    	sort(b+1,b+n+1);cnt=unique(b+1,b+n+1)-b-1;
    	for(int i=1;i<=n;++i)   //建n棵权值线段树
    		update(rt[i],rt[i-1],1,cnt,lower_bound(b+1,b+cnt+1,a[i])-b);
    	while(m--)
    		solve();
    	return 0;
    }
    

    这就是主席树,是不是很简单。

    有人也许会问,知道单点修改的主席树怎么写了,区间修改的怎么写呢?

    它的本质是一样的,只需要把修改的值做一个永久标记在它的祖先们身上,然后求交就可以了

    题单

    KUR-Couriers

    Count on a tree(树上第k大)

    可持久化并查集

    粟粟的书架

    混合果汁

    这篇文章对你有没有帮助呢?有的话,点个赞吧。

    如果有什么不满意的地方,欢迎在评论区反馈

  • 相关阅读:
    报错:Message is larger than modules
    报错:常量字符串过长
    C#监控WinCE手机用户操作的程序,并通过usb连接发送到pc监听服务
    .Net Compact Framework coredll.dll API列表
    Oracle任意日期得到该周第一天的日期
    ORACLE查看锁表进程及杀死进程的语句
    客户端js与服务端通过BASE64进行交互
    为什么在powerdesigner成功将表生成到oracle,用sql操作提示表或视图不存在
    gprof的简单实用
    学习笔记fputs与printf
  • 原文地址:https://www.cnblogs.com/hanruyun/p/9916299.html
Copyright © 2011-2022 走看看