主席树入门详解+题目推荐

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

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

给定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\n",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\n",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大)

可持久化并查集

粟粟的书架

混合果汁

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

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

原文地址:https://www.cnblogs.com/hanruyun/p/9916299.html

时间: 2024-12-12 12:49:15

主席树入门详解+题目推荐的相关文章

单调队列/单调栈入门详解+题目推荐

以前一直以为这两个是很高级的东西,这段时间用到了才开始学,发现实际上非常简单 下面我们以单调队列为例进行讲解,单调栈自行类比 顾名思义 单调队列这个名字就指明了它的性质--单调性 我们来看一道例题--滑动窗口 题面在此不再赘述,大意就是有一个长度为\(n\)的数列,一个长度为\(k\)的窗口,输出窗口位于每个位置下的下的最大最小值 嗯,题目很好理解,st表或者线段树过的先别说话,我们来看看另一种方法 我们维护一个长度为k的队列,使得队列的开头为答案,那么我们每次只需要输出开头就好了.这个想法很好

线段树 入门详解

概念(copy度娘): 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN).而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩. 通俗地讲: 线段树就是把一个线段转变为一个二叉树,如下所示: 一个线段长度为4,则把它变成线段树,就是这个样子 这样如果你想改变一个区间的值,只用O(logn).比一般算法快了许多. 但是空间复杂度就会高一些,比如本来

Linq之旅:Linq入门详解(Linq to Objects)

示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集成查询).通过LINQ,我们可以使用相同API操作不同的数据源.接下来就让我们看看LINQ是什么以及如何使用? 再此之前,需要先了解的相关技术 1. 隐式类型.匿名类型.对象初始化器 1) 隐式类型,使用var关键字创建,C#编译器会根据用于初始化局部变量的初始值推断出变量的数据类型.(不过我个人认

Poj 2104(主席树入门

题目:静态查询区间第k大. 主席树入门题目,之前看的很多资料一上来就是动态区间第k大,看得很费劲,后来找了个写得清晰的,感觉静态的还不算难,代码也不长. /* * @author: Cwind */ //#pragma comment(linker, "/STACK:102400000,102400000") #include <iostream> #include <map> #include <algorithm> #include <cs

【转载】SQL注入攻防入门详解

滴答…滴答…的雨,欢迎大家光临我的博客. 学习是快乐的,教育是枯燥的. 博客园  首页  博问  闪存    联系  订阅 管理 随笔-58 评论-2028 文章-5  trackbacks-0 站长统计|  今日IP[353] | 今日PV[848] | 昨日IP[922] |  昨日PV[2188] |当前在线[10] SQL注入攻防入门详解 =============安全性篇目录============== 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但

svg入门详解

一.svg是什么? SVG 意为可缩放矢量图形(Scalable Vector Graphics). SVG 是使用 XML 来描述二维图形和绘图程序的语言. SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失. SVG 是万维网联盟的标准. 二.svg的优势 与其他图像格式相比(比如 JPEG 和 GIF),使用 SVG 的优势在于: SVG 图像可通过文本编辑器来创建和修改: SVG 图像可被搜索.索引.脚本化或压缩: SVG 是可伸缩的: SVG 图像可在任何的分辨率下被高质量地打

【转】Asp.Net MVC3 简单入门详解过滤器Filter

原文地址:http://www.cnblogs.com/boruipower/archive/2012/11/18/2775924.html 前言 在开发大项目的时候总会有相关的AOP面向切面编程的组件,而MVC(特指:Asp.Net MVC,以下皆同)项目中不想让MVC开发人员去关心和写类似身份验证,日志,异常,行为截取等这部分重复的代码,那我们可以通过AOP截取实现,而在MVC项目中我们就可以直接使用它提供的Filter的特性帮我们解决,不用自己实现复杂的AOP了. 在Asp.net Mvc

webpack入门详解

webpack入门详解(基于webpack 3.5.4  2017-8-22) webpack常用命令: webpack --display-error-details    //执行打包 webpack -w               // 提供watch方法:实时进行打包更新 webpack -p           // 对打包后的文件进行压缩 webpack -d            // 提供source map,方便调式代码 webpack -dev-server --open 

Quartz 入门详解

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的日程序表.Jobs可以做成标准的Java组件或 EJBs.官方网站:http://www.opensymphony.com/quartz 相关Jar:   quartz-all-1.6.0.jar   jta.jar   commons-logging-1.1.jar