博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
洛谷——P3919 【模板】可持久化数组(可持久化线段树/平衡树)
阅读量:5052 次
发布时间:2019-06-12

本文共 7130 字,大约阅读时间需要 23 分钟。

P3919 【模板】可持久化数组(可持久化线段树/平衡树)

 

题目背景

UPDATE : 最后一个点时间空间已经放大

标题即题意

有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集)

题目描述

如题,你需要维护这样的一个长度为 $N$ 的数组,支持如下几种操作

  1. 在某个历史版本上修改某一个位置上的值

  2. 访问某个历史版本上的某一位置的值

此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)

输入输出格式

输入格式:

 

输入的第一行包含两个正整数 $N$,$M$, 分别表示数组的长度和操作的个数。

第二行包含NN个整数,依次为初始状态下数组各位的值(依次为 $a_i$,$1 \leq i \leq N$)。

接下来MM行每行包含3或4个整数,代表两种操作之一(ii为基于的历史版本号):

  1. 对于操作1,格式为$v_i \ 1 \ {loc}_i \ {value}_i$,即为在版本$v_i$的基础上,将 $a_{

    {loc}_i}$ 修改为 ${value}_i$

  2. 对于操作2,格式为$v_i \ 2 \ {loc}_i$,即访问版本$v_i$中的 $a_{

    {loc}_i}$的值

 

输出格式:

 

输出包含若干行,依次为每个操作2的结果。

 

输入输出样例

输入样例#1: 
5 1059 46 14 87 410 2 10 1 1 140 1 1 570 1 1 884 2 40 2 50 2 44 2 12 2 21 1 5 91
输出样例#1: 
598741878846

说明

数据规模:

对于30%的数据:$1 \leq N$, $M \leq {10}^3$

对于50%的数据:$1 \leq N$, $M \leq {10}^4$

对于70%的数据:$1 \leq N$, $M \leq {10}^5$

对于100%的数据:$1 \leq N$, $M \leq {10}^6$, $1 \leq {loc}_i$ $\leq N$, $0 \leq v_i < i$, $-{10}^9 \leq a_i$, ${value}_i \leq {10}^9$

经测试,正常常数的可持久化数组可以通过,请各位放心

数据略微凶残,请注意常数不要过大

另,此题I/O量较大,如果实在TLE请注意I/O优化

询问生成的版本是指你访问的那个版本的复制

样例说明:

一共11个版本,编号从0-10,依次为:

* 0 : 59 46 14 87 41

* 1 : 59 46 14 87 41

* 2 : 14 46 14 87 41

* 3 : 57 46 14 87 41

* 4 : 88 46 14 87 41

* 5 : 88 46 14 87 41

* 6 : 59 46 14 87 41

* 7 : 59 46 14 87 41

* 8 : 88 46 14 87 41

* 9 : 14 46 14 87 41

* 10 : 59 46 14 87 91

 

 

~\(≧▽≦)/~啦啦啦 

身为蒟蒻的我终于入门了可持久化线段树的冰山一角,开森

%%%%,可以直接转到这位大佬的博客学习

大佬可直接转

 

刚入门的蒟蒻对此的理解:

可持久化线段树,支持访问历史版本,修改历史版本,emmmmm

 

显然,你对于每一次版本都新建一颗线段树,会TLE,更会MLE,这种方法行不通

 

观察发现,当你修改一个历史版本时,即创建一个新版本,当前版本相对于历史版本改变的仅仅是那个点所影响的那条链,

 

emmmmm貌似就这么多了

$get$到新技能,非递归版线段树

 

IL void insert(int *T,int u,int l,int r,int k){//T当前节点指针,u原版本对应节点,区间端点,所要修改位置    while(l!=r){        int mid=(l+r)>>1;        *T=++tot;//新增节点,分配空间        if(k<=mid) r=mid,rc[*T]=rc[u],T=&lc[*T],u=lc[u];//如果所要修改的节点在区间中点mid左侧,说明右侧线段树没有改变,T,u更新为当前左子树,右子树连接到历史版本好了,继续        else l=mid+1,lc[*T]=lc[u],T=&rc[*T],u=rc[u];//上同    }    in(val[*T=++tot]);}

 

奉上代码:

#include
#include
#include
#define N 10000000#define IL inlineusing namespace std;IL void in(int &x){ register char c=getchar();x=0;int f=1; for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f;}int rt[N],lc[N],rc[N],tot,val[N];IL void build(int &RT,int l,int r){ RT=++tot; if(l==r) {
in(val[tot]);return;} int mid=(l+r)>>1; build(lc[RT],l,mid); build(rc[RT],mid+1,r);}IL void insert(int *T,int u,int l,int r,int k){ while(l!=r){ int mid=(l+r)>>1; *T=++tot; if(k<=mid) r=mid,rc[*T]=rc[u],T=&lc[*T],u=lc[u]; else l=mid+1,lc[*T]=lc[u],T=&rc[*T],u=rc[u]; } in(val[*T=++tot]);}IL int ask(int t,int l,int r,int k){ while(l!=r){ int m=(l+r)>>1; if(k<=m) r=m,t=lc[t]; else l=m+1,t=rc[t]; } return val[t];}int n,m;int main(){ in(n),in(m); build(rt[0],1,n); for(int opt,v,loc,i=1;i<=m;i++){ in(v),in(opt),in(loc); if(opt&1) insert(&rt[i],rt[v],1,n,loc); else{ printf("%d\n",ask(rt[v],1,n,loc)); rt[i]=++tot; lc[tot]=lc[rt[v]]; rc[tot]=rc[rt[v]]; } } return 0;}

 emmm,来一发递归版的

#include
#include
#include
#define N 100000000using namespace std;int rt[N],P,lc[N],rc[N],val[N];void build(int &R,int l,int r){ R=++P; if(l==r) {scanf("%d",&val[P]);return;} int mid=(l+r)>>1; build(lc[R],l,mid); build(rc[R],mid+1,r);}void insert(int *T,int u,int l,int r,int k){ *T=++P; if(l==r) {scanf("%d",&val[*T]);return;} int mid=(l+r)>>1; if(k<=mid) rc[*T]=rc[u],insert(&lc[*T],lc[u],l,mid,k); else lc[*T]=lc[u],insert(&rc[*T],rc[u],mid+1,r,k);}int ask(int t,int l,int r,int k){ int mid=(l+r)>>1; if(l==r) return val[t]; if(k<=mid) return ask(lc[t],l,mid,k); else return ask(rc[t],mid+1,r,k);}int n,m;int main(){ scanf("%d%d",&n,&m); build(rt[0],1,n); for(int opt,v,loc,i=1;i<=m;i++){ scanf("%d%d%d",&v,&opt,&loc); if(opt==1) insert(&rt[i],rt[v],1,n,loc); else printf("%d\n",ask(rt[v],1,n,loc)),rt[i]=++P,lc[rt[i]]=lc[rt[v]],rc[rt[i]]=rc[rt[v]]; } return 0;}
主席树入门

 

 

以上是主席树单点修改+单点查询的模板。

主席树才A了这一道题,太弱了吧,垃圾(我)

 

在大佬博客发现的一道题(额,又是一道模板题)

主席树区间修改+区间查询模板

 

主要是理解线段树标记永久化的思想  <-可以转这位大佬的博客学习

 

线段树标记永久化,当你所要修改的区间包含于当前区间时,就说明所要修改区间对当前区间产生了贡献,那么当前区间直接加上这个贡献即可,只有当当前区间完全符合修改区间时,

就下方标记(不,其实是添加标记,因为不需要$pushup$),这就是区间修改

IL void Modify(int &t,int u,int l,int r,int v){    t=++tot;//添加新节点     lc[t]=lc[u],rc[t]=rc[u],cov[t]=cov[u],sum[t]=sum[u];//更新新节点     sum[t]+=v*(qr-ql+1);//对此区间产生贡献,此区间直接加上此贡献     if(ql==l&&qr==r) {cov[t]+=v;return;}    int mid=(l+r)>>1;    if(qr<=mid) Modify(lc[t],lc[u],l,mid,v);    else if(ql>mid) Modify(rc[t],rc[u],mid+1,r,v);    else Modify(lc[t],lc[u],l,mid,ql,mid),Modify(rc[t],rc[u],mid+1,r,mid+1,qr);    //若所修改区间qr在mid左侧,直接转到左子树,     //若所修改区间ql在mid右侧,直接转到右子树,     //反之,同时转向左右子树 }

 接下来考虑区间查询,由上到下,累加标记,若当前区间有标记,那么对查询区间肯定有影响,要累加这个标记,

最后在加上所查询区间的$sum$值即可。

LL query(LL t,LL l,LL r,LL ql,LL qr){    if(l==ql&&r==qr) return sum[t];    int mid=(l+r)>>1;    LL res=cov[t]*(qr-ql+1);    if(qr<=mid) res+=query(lc[t],l,mid,ql,qr);    else if(ql>mid) res+=query(rc[t],mid+1,r,ql,qr);    else res+=query(lc[t],l,mid,ql,mid),res+=query(rc[t],mid+1,r,mid+1,qr);    return res;}

完整代码:

#include
#include
#include
#include
#define N 10000000#define IL inline #define LL long longusing namespace std;void in(LL &x){ register char c=getchar();x=0;LL f=1; while(!isdigit(c)){
if(c=='-') f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} x*=f;}LL rt[N],lc[N],rc[N],tot,sum[N],cov[N];IL void build(LL &t,LL l,LL r){ t=++tot; if(l==r){
in(sum[t]);return;} LL m=(l+r)>>1; build(lc[t],l,m); build(rc[t],m+1,r); sum[t]+=sum[lc[t]]+sum[rc[t]];}IL void Modify(LL &t,LL u,LL l,LL r,LL ql,LL qr,LL v){ t=++tot; lc[t]=lc[u],rc[t]=rc[u],cov[t]=cov[u],sum[t]=sum[u]; sum[t]+=v*(qr-ql+1); if(ql==l&&qr==r) {cov[t]+=v;return;} LL mid=(l+r)>>1; if(qr<=mid) Modify(lc[t],lc[u],l,mid,ql,qr,v); else if(ql>mid) Modify(rc[t],rc[u],mid+1,r,ql,qr,v); else Modify(lc[t],lc[u],l,mid,ql,mid,v),Modify(rc[t],rc[u],mid+1,r,mid+1,qr,v);}LL query(LL t,LL l,LL r,LL ql,LL qr){ if(l==ql&&r==qr) return sum[t]; int mid=(l+r)>>1; LL res=cov[t]*(qr-ql+1); if(qr<=mid) res+=query(lc[t],l,mid,ql,qr); else if(ql>mid) res+=query(rc[t],mid+1,r,ql,qr); else res+=query(lc[t],l,mid,ql,mid),res+=query(rc[t],mid+1,r,mid+1,qr); return res;}LL n,m;int main(){ in(n),in(m); build(rt[0],1,n); LL T=0; for(LL l,r,d,i=1;i<=m;i++){ char x; cin>>x; if(x=='B'){ in(d); T=d; tot=rt[T+1]-1;//回收空间 }else{ in(l),in(r); if(x=='C') in(d),++T,Modify(rt[T],rt[T-1],1,n,l,r,d); if(x=='Q') printf("%lld\n",query(rt[T],1,n,l,r)); if(x=='H') in(d),printf("%lld\n",query(rt[d],1,n,l,r)); } } return 0;}

 

只会抄题解,抄完之后还镇定自若地跟你们瞎$BB$地蒟蒻。qwq。。。

(下一道模板题)

转载于:https://www.cnblogs.com/song-/p/9763239.html

你可能感兴趣的文章
noSQL数据库相关软件介绍(大数据存储时候,必须使用)
查看>>
iOS开发——缩放图片
查看>>
HTTP之URL的快捷方式
查看>>
满世界都是图论
查看>>
配置链路聚合中极小错误——失之毫厘谬以千里
查看>>
代码整洁
查看>>
蓝桥杯-分小组-java
查看>>
Java基础--面向对象编程1(类与对象)
查看>>
Android Toast
查看>>
iOS开发UI篇—Quartz2D使用(绘制基本图形)
查看>>
docker固定IP地址重启不变
查看>>
[Swift]LeetCode128. 最长连续序列 | Longest Consecutive Sequence
查看>>
[Swift通天遁地]一、超级工具-(9)在地图视图MKMapView中添加支持交互动作的标注图标...
查看>>
js版base64()
查看>>
poj3006---素数筛法
查看>>
c语言结构体排序示例
查看>>
openresty nginx systemtap netdata
查看>>
[Angular] Make a chatbot with DialogFlow
查看>>
sd卡无法启动及zc706更改主频后可以进入uboot无法启动kernel的坑
查看>>
代理模式
查看>>