题目链接:点击打开链接
题目大意:
给你一串序列,每次对它有四种操作,
1. 将l到r之间所有的数+d,同时时间戳+1。
2. 查询当前时间戳下l到r的所有数的总和。
3..查询时间戳d下的l到r的所有数的和。
4.将时间戳返回至t。
解题思路:
如果没有时间戳概念的话其实本质就是一个非常简单的线段树的求和。但是引入了时间戳的概念之后,就变得复杂了许多。其实本质上变为了一个主席树的问题,但是主席树正常情况下是用来解决某个结点变化而引起的整个树的变化,这道题如果按照正常的主席树的建树方法,空间是非常的大的,在某些极端的数据下,其实就是下相当于新建了一个线段树。那么如何才能减少空间的使用呢?
其实跟线段树的懒惰标记差不多,对于被改变的结点,记录改变之后的值,如果某个结点包含的整个区域都被改变了,那么在当前结点上添加一个变化值,查询当前结点内部某些点的和的时候,直接利用区间长度 *变化量即可。当然我们的主席树维护的只是变化量。具体有点难以解释,可以看代码自行理解。
Ac代码:
#include
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int INF=1e9+7;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int cnt,n,m,root[N],ti;
ll s[N],ans; //注意long long
struct node //主席树结点
{
int l,r;
ll sum,add;
}t[N*40];
char sk;
void update(int l,int r,int pl,int pr,int &x,int y,int k) //主席树的更新操作
{
t[++cnt]=t[y],t[cnt].sum+=(min(r,pr)-max(l,pl)+1)*k,x=cnt; //记录当前区间内总的变化值
if(pl<=l&&r<=pr) //该节点所有点均被修改 直接添加变化值即可
{
t[cnt].add+=k;
return ;
}
int mid=(l+r)/2; //选择更新的方向
if(pl<=mid)
update(l,mid,pl,pr,t[x].l,t[y].l,k);
if(pr>mid)
update(mid+1,r,pl,pr,t[x].r,t[y].r,k);
}
ll query(int l,int r,int pl,int pr,int rt) //查询操作
{
if(pl<=l&&r<=pr) //当前区间在查询区间内 直接return sum
return t[rt].sum;
ll ans=t[rt].add*(min(r,pr)-max(l,pl)+1); //如果当前区间不在查询区间内 添加上变化值
int mid=(l+r)/2;
if(pl<=mid)
ans+=query(l,mid,pl,pr,t[rt].l);
if(pr>mid)
ans+=query(mid+1,r,pl,pr,t[rt].r);
return ans;
}
int main()
{
int flag=1;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(!flag)
printf("\n");
ti=0,flag=0;
memset(root,0,sizeof root);
for(int i=1;i<=n;i++)
scanf("%lld",&s[i]),s[i]+=s[i-1];
while(m--)
{
int l,r,d;
scanf(" %c",&sk);
if(sk=='C')
{
scanf("%d%d%d",&l,&r,&d);
ti++;
update(1,n,l,r,root[ti],root[ti-1],d);
}
if(sk=='Q')
{
scanf("%d%d",&l,&r);
ans=s[r]-s[l-1];
ans+=query(1,n,l,r,root[ti]); //我们query所求的只是变化值
printf("%lld\n",ans);
}
if(sk=='H')
{
scanf("%d%d%d",&l,&r,&d);
ans=s[r]-s[l-1];
ans+=query(1,n,l,r,root[d]);
printf("%lld\n",ans);
}
if(sk=='B')
{
scanf("%d",&d);
ti=d;
cnt=root[ti+1]; //这一步可以大大的减少内存消耗
}
}
}
}