【HDU 4348】 To the moon 可持久化线段树
题目链接:传送门
简述题意:
一个长度为n的数组,4种操作 :
(1)C l r d:区间[l,r]中的数都加1,同时当前的时间戳加1 。
(2)Q l r:查询当前时间戳区间[l,r]中所有数的和 。
(3)H l r t:查询时间戳t区间[l,r]的和 。
(4)B t:将当前时间戳置为t 。回到过去t时刻,t之后的信息都会消失,即就不会再向前跳跃。
所有操作均合法 。
起始的时间戳为 0.
这道题让我明白什么是永久化标记,我做这道题最开始的想法,利用时间戳建树,但是询问和普通线段树一样 lazy 标记,然后pushdown和pushup,结果样例对了,但还是会WA,造了几组样例还是WA(难受。。。),原因在于,向下传递的时候,把原来树的状态给改变了(可以帮助你理解为什么用永久化标记)。
贴一份我的错误代码:https://paste.ubuntu.com/p/rd9yZ7R75z/
下面是错误的样例.
正确代码:
///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<bitset>
#include<set>
#include<stack>
#include<map>
#include<list>
#include<new>
#include<vector>
#define MT(a,b) memset(a,b,sizeof(a));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const double E=2.718281828459;
const ll mod=1e9+7;
const int INF=0x3f3f3f3f;
const int maxn=1e5+10;
struct node
{
int l;
int r;
ll sum;
ll lazy;
} point[4000005];
int root[100005];
int nowt,counts;
int buildtree(int l,int r)
{
int s=++counts;
point[s].lazy=0;
if(l==r)
{
scanf("%lld",&point[s].sum);
return s;
}
int mid=(l+r)>>1;
point[s].l=buildtree(l,mid);
point[s].r=buildtree(mid+1,r);
point[s].sum=point[point[s].l].sum+point[point[s].r].sum;
return s;
}
int updateroot(int left,int l,int r,int a,int b,ll x)
{
int s=++counts;
point[s]=point[left];
point[s].sum+=(b-a+1)*x;
///[l,r]区间完全包含[a,b],该区间加上(b-a+1)*x;
if(l==a&&r==b)
{
point[s].lazy+=x;
///永久标记:标记整个区间+x,用于询问[l,r]子区间,但是不包括自身,因为上面已经更新区间和了.
return s;
}
int mid=(l+r)>>1;
if(b<=mid)
point[s].l=updateroot(point[left].l,l,mid,a,b,x);
else if(a>mid)
point[s].r=updateroot(point[left].r,mid+1,r,a,b,x);
else
{
point[s].l=updateroot(point[left].l,l,mid,a,mid,x);
point[s].r=updateroot(point[left].r,mid+1,r,mid+1,b,x);
}
return s;
}
ll find_sum(int t,int l,int r,int a,int b)
{
if(l==a&&r==b)
return point[t].sum;
ll s=(b-a+1)*point[t].lazy;
///如果查询的区间是[l,r]的子区间,并且[l,r]被标记过,则需要加上需要增加的值.
int mid=(l+r)>>1;
if(b<=mid)
return find_sum(point[t].l,l,mid,a,b)+s;
else if(a>mid)
return find_sum(point[t].r,mid+1,r,a,b)+s;
else
return find_sum(point[t].l,l,mid,a,mid)+find_sum(point[t].r,mid+1,r,mid+1,b)+s;
}
int main()
{
int n,m,l,r,t;
ll x;
char c[3];
scanf("%d %d",&n,&m);
counts=0;
nowt=0;
root[0]=buildtree(1,n);
while(m--)
{
scanf("%s",c);
if(c[0]=='Q')
{
scanf("%d %d",&l,&r);
printf("%lld\n",find_sum(root[nowt],1,n,l,r));
}
else if(c[0]=='H')
{
scanf("%d %d %d",&l,&r,&t);
printf("%lld\n",find_sum(root[t],1,n,l,r));
}
else if(c[0]=='C')
{
scanf("%d %d %lld",&l,&r,&x);
root[nowt+1]=updateroot(root[nowt],1,n,l,r,x);
nowt++;
}
else
{
scanf("%d",&t);
nowt=t;
}
}
return 0;
}