主席树的修改一般是只涉及单点修改的,因为区间操作如果我们pushdown的话,相当于又涉及了两个孩子节点的修改,一直递归下去,这样空间要去就和建n棵线段树差不多一个量级了。
我们可以把懒标记永久打在节点上,不下传,只在pushup和查询的时候用上。
具体来说,pushup的时候除了合并左右子节点,再考虑一下根节点区间的懒标记的影响。
查询的时候,查询路径上经过节点的懒标记都要合并起来,一路记录下去,最终在查询区间进行计算。
涉及回退历史版本时,要注意是否是时间上的回溯。
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){while(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){while(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,m;
ll tag[maxn*40],tree[maxn*40];
int lc[maxn*40],rc[maxn*40],tot;
int root[maxn];
//给定序列 访问历史版本 区间查询 区间更新
//主席树的区间修改 标记永久化
inline void pushup(int rt,int l,int r)
{
//对于区间[l,r]而言 tag[rt]并没有传递更新给lc rc 所以除了lc rc的和还得加上tag[rt]的贡献
tree[rt]=tree[lc[rt]] + tree[rc[rt]] + tag[rt]*(r-l+1);
}
inline void build(int &rt,int l,int r)
{
rt=++tot;
tag[rt]=0;
lc[rt]=0,rc[rt]=0;
if(l==r)
{
scanf("%lld",&tree[rt]);
return ;
}
int mid=l+r>>1;
build(lc[rt],l,mid);
build(rc[rt],mid+1,r);
pushup(rt,l,r);
}
inline void upd(int &rt1,int rt2,int l,int r,int vl,int vr,int v)
{
if(r<vl || l>vr) return ;
rt1=++tot;
tree[rt1]=tree[rt2];
lc[rt1]=lc[rt2],rc[rt1]=rc[rt2];
tag[rt1]=tag[rt2];//tag也得继承
if(vl<=l && r<=vr)
{
tag[rt1]+=v;
tree[rt1]+=1ll*(r-l+1)*v;
return ;
}
int mid=l+r>>1;
upd(lc[rt1],lc[rt2],l,mid,vl,vr,v);
upd(rc[rt1],rc[rt2],mid+1,r,vl,vr,v);
pushup(rt1,l,r);
}
inline ll qry(int rt,int l,int r,int vl,int vr,ll add)
{
if(!rt) return 0;
if(vl<=l && r<=vr)
{
return tree[rt]+add*(r-l+1);
}
int mid=l+r>>1;
if(vr<=mid) return qry(lc[rt],l,mid,vl,vr,add+tag[rt]);//一路把遇到的懒标记都合并起来
else if(vl>mid) return qry(rc[rt],mid+1,r,vl,vr,add+tag[rt]);
return qry(lc[rt],l,mid,vl,vr,add+tag[rt]) + qry(rc[rt],mid+1,r,vl,vr,add+tag[rt]);
}
int main()
{
while(~scanf("%d %d",&n,&m))
{
tot=0;
build(root[0],1,n);//初始的时刻是0
int clk=0;
char op[3];
int l,r,t;
while(m--)
{
scanf("%s",op);
if(op[0]=='C')//区间更新
{
scanf("%d %d %d",&l,&r,&t);
upd(root[clk+1],root[clk],1,n,l,r,t);
++clk;
}
else if(op[0]=='Q') //查询当前版本区间和
{
scanf("%d %d",&l,&r);
printf("%lld\n",qry(root[clk],1,n,l,r,0));
}
else if(op[0]=='H') //查询历史版本区间和
{
scanf("%d %d %d",&l,&r,&t);
printf("%lld\n",qry(root[t],1,n,l,r,0));
}
else //回退到t版本 这里不能写成root[clk]=root[t] 因为题目要求是回到第t时刻
//回去之后 下一时刻是t+1时刻 而不是clk+1时刻
{
scanf("%d",&t);
clk=t;
}
}
}
return 0;
}