题目链接:点击查看
题目大意:给出一个初始时长度为 n 的序列,有 m 次操作,每种操作分为下列四种类型:
- C l r d:新建一个继承了前一个版本的数组,并将区间 [ l , r ] 内的数字都加上 d
- Q l r:访问最后一个版本的数组,[ l , r ] 内的区间和
- H l r t:访问第 t 个版本的数组,[ l , r ] 内的区间和
- B t:回溯到第 t 个版本
题目分析:如果不考虑变量 t ,也就是不同版本的数组所带来的影响,那么这就是线段树的一道经典区间修改和区间查询的题目,写一个 lazy 下传标记即可
考虑有多个不同版本的数组,也就对应了主席树,换句话说这个题目考察的正是主席树的区间修改和区间查询
主席树的单点修改比较简单,每次只会涉及到一条链,即 logn 条节点,直接新建,其余的节点继承上一个位置的即可
区间修改的话如果考虑 lazy 变量的下传,最坏的情况下需要从根节点下传到叶子节点,一次操作的时空复杂度都是 nlogn 级别的
那可能有的同学会说了:我就先存一下 lazy 标记,等查询的时候再下传不就行了?
很遗憾的说,这样的时间复杂度单次操作也将会是 nlogn 级别的,因为不同版本的线段树可能 lazy 标记不同,导致 nlogn 个节点的值最终都不相同,所以都需要新建才行
所以这里需要引入一个新的概念,也就是线段树的标记永久化
对于普通线段树的 lazy 标记下传操作,也就是 pushdown 函数,对应过去就是:线段树中每个节点的值都是真实的值
但标记永久化顾名思义,也就是 lazy 标记不下传,这样线段树中的每个节点对应的值此时并不是真实的值,但在执行 query 操作时,一定会从其父节点下来,此时额外维护一个变量,用于记录沿途路径上的 lazy 的贡献,最后统一计算一样可以得到真实答案
但在执行标记永久化时需要注意的细节就是,不能使用 pushup 函数自底向上去传递贡献,只能自顶向下去维护每个节点的贡献,到达需要返回的位置时,打一个 lazy 标记直接返回即可,这样的操作带来的影响是:相应的区间上面的父节点中储存的是真实值,而 lazy 标记下面的那些节点中存在的都是一个虚拟的值,需要与祖先节点中的 lazy 标记的贡献进行加和才能得到真实答案
使用标记永久化的一个好处就是,每次修改仍然是至多修改 logn 个节点,这样就能实现主席树的区间修改的操作了
对于这个题目而言,注意一下空间大小,因为初始时需要建立一棵线段树,这里需要 4 * n ,然后每次修改都需要新建 logn 条树链,设 n 与 m 同阶,最终需要新建 nlogn 个树链,总的空间复杂度也就是 n ( 4 + nlogn ),logn 记为 20,所以开 25 * n 就好了
代码:
//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e5+100;
struct Node
{
int l,r;
LL sum,lazy;
}tree[N*25];
int cnt,root[N];
void build(int &k,int l,int r)
{
k=cnt++;
tree[k].lazy=0;
if(l==r)
{
scanf("%lld",&tree[k].sum);
return;
}
int mid=l+r>>1;
build(tree[k].l,l,mid);
build(tree[k].r,mid+1,r);
tree[k].sum=tree[tree[k].l].sum+tree[tree[k].r].sum;
}
void update(int &k,int l,int r,int L,int R,LL val)//[l,r]:目标区间 [L,R]:当前区间
{
tree[cnt++]=tree[k];
k=cnt-1;
tree[k].sum+=val*(r-l+1);
if(L>=l&&R<=r)
{
tree[k].lazy+=val;
return;
}
int mid=L+R>>1;
if(r<=mid)
update(tree[k].l,l,r,L,mid,val);
else if(l>mid)
update(tree[k].r,l,r,mid+1,R,val);
else
{
update(tree[k].l,l,mid,L,mid,val);
update(tree[k].r,mid+1,r,mid+1,R,val);
}
}
LL query(int k,int l,int r,int L,int R,LL add)//[l,r]:目标区间 [L,R]:当前区间
{
if(R<l||L>r)
return 0;
if(L>=l&&R<=r)
return tree[k].sum+add*(R-L+1);
int mid=L+R>>1;
return query(tree[k].l,l,r,L,mid,add+tree[k].lazy)+query(tree[k].r,l,r,mid+1,R,add+tree[k].lazy);
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
cnt=1;
build(root[0],1,n);
int time=0;
while(m--)
{
char op[5];
scanf("%s",op);
if(op[0]=='C')
{
int l,r,d;
scanf("%d%d%d",&l,&r,&d);
time++;
root[time]=root[time-1];
update(root[time],l,r,1,n,d);
}
else if(op[0]=='Q')
{
int l,r;
scanf("%d%d",&l,&r);
printf("%lld\n",query(root[time],l,r,1,n,0));
}
else if(op[0]=='H')
{
int l,r,t;
scanf("%d%d%d",&l,&r,&t);
printf("%lld\n",query(root[t],l,r,1,n,0));
}
else if(op[0]=='B')
{
scanf("%d",&time);
}
}
}
return 0;
}