可持久化线段树
1.1可持久化
顾名思义,数据的可持久化就是不仅能访问该文件的当前版本,也能访问该文件的历史版本,比较常见的应用就是撤销了,而本篇博文要写的就是线段树可持久化的实现。如果没学过线段树的话请先学习线段树以及线段树的动态开点。
1.2实现原理
考虑最简单的情况:
这是一棵非常简单的线段树,设为0号,现在,我们要改变点1的值,(即节点2),生成1号的历史版本,该怎么做呢?
考虑到改变一个点,只要将该点和它所有的祖宗改一下就可以了,其他的节点不用变,如下所示:
该图中,我们新增了5号节点代表点1的值,即虽然2号,5号的区间一样,但值不一样,新增了4号点作为新版本中的根节点,由于4号点的右孩子没改,所以没有新增节点,在新版本中代表区间2的节点依然是3号点。
读者们也注意到了,图中新增了两个带箭头的数:0、1,它们代表了历史版本所指向的根节点,这样就能锁定该历史版本的整棵树了。
那么,当树更复杂呢?我们可以考虑一下,将1号,4号节点视为一棵线段树的子树的根,而指向它们的是它们父亲节点,再将2、3节点视为1节点的子树,同样可以求解。
2、代码实现
这次总算有【模板】可持久化数组(可持久化线段树/平衡树)了,不用博主自己编题了……
这可能要动态开点线段树的知识。
该题中,我们只要实现单点修改,单点查询,我们需要两个数组记录该节点的左右儿子,若要修改的值在左儿子,那么该节点的右儿子就是模式版本的右儿子,再做左儿子就行了。
void jia(long long mo,long long u,long long x,long long k)//mo:模式版本的代表该区间的节点,u:我们要构造的节点,x:区间位置,k:要修改成的值 { l[u]=l[mo]; r[u]=r[mo]; if (l[u]==r[u]) { z[u]=k; return; }//这个和build很像 if (x<=(l[u]+r[u])/2)//x在左儿子 { rr[u]=rr[mo];//右边和模式版本一样 cnt++; ll[u]=cnt; jia(ll[mo],ll[u],x,k); z[u]=z[ll[u]]+z[rr[u]]; } else { ll[u]=ll[mo];//左边就和模式版本一样 cnt++; rr[u]=cnt; jia(rr[mo],rr[u],x,k); z[u]=z[ll[u]]+z[rr[u]];//x在右儿子
} }
单点修改算是可持久化的精髓,以下就是完整代码:
#include<bits/stdc++.h> using namespace std; long long l[40000001],r[40000001],z[40000001],ll[40000001],rr[40000001],cnt,n,m,i,loc,v,val,q,a[1000001],he[1000001];//空间可不止4倍,博主比较懒,开了40倍。 void build(long long u,long long l1,long long r1) { l[u]=l1; r[u]=r1; if (l1==r1) { z[u]=a[l1]; return; } cnt++; ll[u]=cnt;//动态开点左区间 build(ll[u],l1,(l1+r1)/2); cnt++; rr[u]=cnt;//动态开点右区间 build(rr[u],(l1+r1)/2+1,r1); z[u]=z[ll[u]]+z[rr[u]];//其实这个用不到 } void jia(long long mo,long long u,long long x,long long k)//单点修改 { l[u]=l[mo]; r[u]=r[mo]; if (l[u]==r[u]) { z[u]=k; return; } if (x<=(l[u]+r[u])/2) { rr[u]=rr[mo]; cnt++; ll[u]=cnt; jia(ll[mo],ll[u],x,k); z[u]=z[ll[u]]+z[rr[u]]; } else { ll[u]=ll[mo]; cnt++; rr[u]=cnt; jia(rr[mo],rr[u],x,k); z[u]=z[ll[u]]+z[rr[u]]; } } long long qui(long long u,long long x)//这和普通线段树的区别就是儿子一个是ll[u],rr[u],一个是u*2,u*2+1。 { if (l[u]>x||r[u]<x) return 0; if (l[u]==r[u]) return z[u]; else return qui(ll[u],x)+qui(rr[u],x); } int main() { he[0]=1; cnt=1;//其实这个也是要不要无所谓…… scanf("%lld%lld",&n,&m); for (i=1;i<=n;i++) scanf("%lld",&a[i]); build(1,1,n); for (i=1;i<=m;i++) { scanf("%lld%lld",&v,&q);; if (q==1) { cnt++; he[i]=cnt;//新增一个版本 scanf("%lld%lld",&loc,&val); jia(he[v],he[i],loc,val); } else { scanf("%lld",&loc); he[i]=he[v];。。直接就将新版本指向模式版本的根节点 printf("%lld\n",qui(he[v],loc)); } } return 0; }
这道题还是相对简单的。接下来难的可就来了。
3、进阶运用
博主总算把这道题编好了:【模板】可持久化线段树(标准版),太难编了。
在这道题中,我们发现从单点修改到了区间修改!我们对区间每一点赋值肯定是不现实的,所以我们就需要一些类似线段树的做法:
void xiafang(long long u)//类似线段树的标记下传 { cnt++; ll[u]=cnt; l[cnt]=l[u]; r[cnt]=(l[u]+r[u])/2; z[cnt]=c[u]*(r[cnt]-l[cnt]+1);//实际上是新造了它的左、右儿子 c[cnt]=c[u]; cnt++; rr[u]=cnt; l[cnt]=(l[u]+r[u])/2+1; r[cnt]=r[u]; z[cnt]=c[u]*(r[cnt]-l[cnt]+1);//标记也要下传 c[cnt]=c[u]; c[u]=0; }
然后,就是区间加了:
void jia(long long mo,long long u,long long l1,long long r1,long long k) { l[u]=l[mo]; r[u]=r[mo]; if (l[u]>=l1&&r[u]<=r1)//如果完全在赋值区间就不继续了 { z[u]=k*(r[u]-l[u]+1); c[u]=k;//打上标记 return; } if (c[mo]) xiafang(mo); if (r1<=(l[u]+r[u])/2)//在右边 { rr[u]=rr[mo]; cnt++; ll[u]=cnt; jia(ll[mo],ll[u],l1,r1,k); z[u]=z[ll[u]]+z[rr[u]]; } else if (l1>(l[u]+r[u])/2)//在左边 { ll[u]=ll[mo]; cnt++; rr[u]=cnt; jia(rr[mo],rr[u],l1,r1,k); z[u]=z[ll[u]]+z[rr[u]]; } else { cnt++; ll[u]=cnt; jia(ll[mo],ll[u],l1,r1,k);//两边都有 cnt++; rr[u]=cnt; jia(rr[mo],rr[u],l1,r1,k); z[u]=z[ll[u]]+z[rr[u]]; } }
因为所有数字小于1e8,所以最大时不会爆long long的。
以下是完整代码:
#include<bits/stdc++.h> using namespace std; long long c[40000001],l[40000001],r[40000001],z[40000001],ll[40000001],rr[40000001],cnt,n,m,i,l1,r1,v,val,q,a[1000001],he[1000001]; void xiafang(long long u) { cnt++; ll[u]=cnt; l[cnt]=l[u]; r[cnt]=(l[u]+r[u])/2; z[cnt]=c[u]*(r[cnt]-l[cnt]+1); c[cnt]=c[u]; cnt++; rr[u]=cnt; l[cnt]=(l[u]+r[u])/2+1; r[cnt]=r[u]; z[cnt]=c[u]*(r[cnt]-l[cnt]+1); c[cnt]=c[u]; c[u]=0; } void build(long long u,long long l1,long long r1) { l[u]=l1; r[u]=r1; if (l1==r1) { z[u]=a[l1]; return; } cnt++; ll[u]=cnt; build(ll[u],l1,(l1+r1)/2); cnt++; rr[u]=cnt; build(rr[u],(l1+r1)/2+1,r1); z[u]=z[ll[u]]+z[rr[u]]; } void jia(long long mo,long long u,long long l1,long long r1,long long k) { l[u]=l[mo]; r[u]=r[mo]; if (l[u]>=l1&&r[u]<=r1) { z[u]=k*(r[u]-l[u]+1); c[u]=k; return; } if (c[mo]) xiafang(mo); if (r1<=(l[u]+r[u])/2) { rr[u]=rr[mo]; cnt++; ll[u]=cnt; jia(ll[mo],ll[u],l1,r1,k); z[u]=z[ll[u]]+z[rr[u]]; } else if (l1>(l[u]+r[u])/2) { ll[u]=ll[mo]; cnt++; rr[u]=cnt; jia(rr[mo],rr[u],l1,r1,k); z[u]=z[ll[u]]+z[rr[u]]; } else { cnt++; ll[u]=cnt; jia(ll[mo],ll[u],l1,r1,k); cnt++; rr[u]=cnt; jia(rr[mo],rr[u],l1,r1,k); z[u]=z[ll[u]]+z[rr[u]]; } } long long qui(long long u,long long l1,long long r1) { if (l[u]>r1||r[u]<l1) return 0; if (l[u]>=l1&&r[u]<=r1) return z[u]; if (c[u]) xiafang(u); return (qui(ll[u],l1,r1)+qui(rr[u],l1,r1))%998244353; } int main() { he[0]=1; cnt=1; scanf("%lld%lld",&n,&m); for (i=1;i<=n;i++) scanf("%lld",&a[i]); build(1,1,n); for (i=1;i<=m;i++) { scanf("%lld%lld",&v,&q);; if (q==1) { cnt++; he[i]=cnt; scanf("%lld%lld%lld",&l1,&r1,&val); jia(he[v],he[i],l1,r1,val); } else { scanf("%lld%lld",&l1,&r1); he[i]=he[v]; printf("%lld\n",qui(he[v],l1,r1)%998244353); } } return 0; }