先说题目问题的结论:
因为在区间修改时需要用回溯更新父节点(tree[p]=tree[p<<1]+tree[p<<1|1]),此时更新时,某些子节点可能是“错误的”,即没有下放懒散标记。用没有下放标记的错误子节点更新时,会覆盖掉正确的父节点的值。
!!!!!注意!!!!!!懒散标记更新tree[p]时要×(r-l+1)!!!!!!!!!!!
lazy_tag 懒散标记,如果对某个区间添加懒散标记,则表示这一个区间整个都需要进行某个整体修改,这个区间能被分成的若干个区间也需要进行修改。
当更高的节点当收到这个懒散标记的命令的时,自我更新完毕后,把这个懒散标记“扣下”不下放,等待查询的时候才临时下放。
可以生动的理解为:(所有人都能读懂!!!)
国家要对某些特殊人群(指定区间)下放补助(区间修改),但这些特殊人群的分部比较散乱,有的恰好在一整个省、在一整个区还有点是个体(不同层次的区间)。国家先把补助下放到各省,如果某个省不是所有人都是补助对象的话,贪官觉得数量过少不会把补助克扣,会把这个补助继续下发。
直到所有人都是补助对象的某个区的贪官手里时,他就会把这笔钱克扣下,同时在自己管理区域的账面上作假(得到懒散标记后先自己更新),国家得到假账后用假账更新国家的账面(回溯更新父节点)。但此时贪官所在的区域之下的市、区、乡镇、个人的账目都没有更新,如果回溯用到这些补助被上层克扣(懒散标记为下发)的基层账目的话就会出错。
所以在第二次下放补助金时(再次区间修改时),由于可能存在用账目被克扣的区域的账目回溯,这样懒散标记所在的区的领导就会暴露,所以为了避免暴露必须下放懒散标记。
同时在领导下来视察(区间查询)之前,也要把标记下放给子节点。
写一个区间和的代码:
落谷P3372
#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
//#define mid (l+r)>>1
#define lson l,mid,p<<1
#define rson mid+1,r,p<<1|1
const int Mn = 100005;
long long tree[Mn*4],tag[Mn*4],ans,a[Mn];
inline void update(int p){tree[p]=tree[p<<1]+tree[p<<1|1];}
inline void build(int l,int r,int p){
if(l == r){
tree[p] = a[l];
return ;
}
int mid = (l+r)>>1;
build(lson);
build(rson);
update(p);
}
void downdate(int l,int r,int p,ll k){
if(!k) return ;
int mid = (l+r)>>1;
tag[p<<1] += k;
tree[p<<1] += (mid-l+1)*k;
tag[p<<1|1] += k;
tree[p<<1|1] += (r-mid)*k;
tag[p] = 0;
}
void change_range(int l,int r,int p,int ql,int qr,long long k){
if(ql<=l&&qr>=r){
tag[p] += k;
tree[p] += (r-l+1)*k;
return ;
}
int mid = (l+r)>>1;
downdate(l,r,p,tag[p]);//为了确保回溯时的子节点更新父节点的正确性,需要下放懒散标记
if(ql<=mid)change_range(lson,ql,qr,k);
if(qr>mid)change_range(rson,ql,qr,k);
update(p);
}
void Query(int l,int r,int p,int ql,int qr){
if(ql<=l&&qr>=r){
ans += tree[p];
return ;
}
downdate(l,r,p,tag[p]);
int mid = (l+r)>>1;
if(ql<=mid) Query(lson,ql,qr);
if(qr>mid) Query(rson,ql,qr);
return ;
}
int main()
{
//std::ios::sync_with_stdio(0);
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i <= n;i++)scanf("%lld",&a[i]);
build(1,n,1);
int q,x,y;
ll k;
while(m--){
scanf("%d",&q);
if(q == 1){
scanf("%d%d%lld",&x,&y,&k);
change_range(1,n,1,x,y,k);
}else{
scanf("%d%d",&x,&y);
ans = 0;
Query(1,n,1,x,y);
printf("%lld\n",ans);
}
}
return 0;
}
//Without simplicity,will lose a lot;without rigor,will lose everything
/*5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
11
8
20*/
补充:区间修改不一定只有把某个区间内所有的数都加上k一种修改方式,还可能同时有把某个区间所有的数都乘上k。不过,这种情况也可以用我们刚才的贪官模型解释。
例如:落谷P3373
将某个区间都乘上k的情况相当于,国家给某个地区的补贴是调高为补贴k倍,这样的话,此前所有的补贴都要被调高k倍,之后的补贴不受这k倍的影响,也就是我们不需要考虑增加补贴和倍数补贴的顺序,当接收到倍数补贴的时候一股脑的把此前所有的增加补贴和群众已有收入翻k倍。相当于把倍数补贴提到最前面。当然下放的时候也是先下放倍数补贴。
不过要注意的是倍数补贴的初始化值应该是1,增加补贴是0。
贴一个AC代码
#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
ll Mod;
const int Mn = 100005;
ll tree[Mn*4],tag_c[Mn*4],tag_j[Mn*4],a[Mn],ans;
#define lson l,mid,p<<1
#define rson mid+1,r,p<<1|1
inline void update(int p){
tree[p] = tree[p<<1]+tree[p<<1|1];
}
inline void build(int l,int r,int p){
if(l==r){
tree[p] = a[l];
return ;
}
int mid = (l+r)>>1;
build(lson);
build(rson);
update(p);
}
inline void downdate(int l,int r,int p,ll k,int vis){
int mid = (l+r)>>1;
if(vis == 2){
tag_j[p<<1] += k;
tag_j[p<<1]%=Mod;
tree[p<<1] += (mid-l+1)*k;
tree[p<<1]%=Mod;
tag_j[p<<1|1] += k;
tag_j[p<<1|1]%=Mod;
tree[p<<1|1] += (r-mid)*k;
tree[p<<1|1]%=Mod;
tag_j[p] = 0;
}
else{
tag_c[p<<1] *= k;
tag_c[p<<1]%=Mod;
tag_j[p<<1] *= k;
tag_j[p<<1] %= Mod;
tree[p<<1] *= k;
tree[p<<1]%=Mod;
tag_c[p<<1|1] *= k;
tag_c[p<<1|1]%=Mod;
tag_j[p<<1|1] *= k;
tag_j[p<<1|1] %= Mod;
tree[p<<1|1] *= k;
tree[p<<1|1]%=Mod;
tag_c[p] = 1;
}
}
inline void change_range(int l,int r,int p,int ql,int qr,ll k,int vis){
if(ql<=l&&qr>=r){
if(vis == 2){
tag_j[p]+=k;
tag_j[p]%=Mod;
tree[p] += ((r-l+1)*k)%Mod;
tree[p] %= Mod;
return ;
}else{
tag_c[p]*=k;
tag_c[p]%=Mod;
tag_j[p] *= k;
tag_j[p] %= Mod;
tree[p] *= k;
tree[p] %= Mod;
return ;
}
}
int mid = (l+r)>>1;
if(tag_c[p]!=1){
downdate(l,r,p,tag_c[p],1);
}
if(tag_j[p]) downdate(l,r,p,tag_j[p],2);
if(ql<=mid) change_range(lson,ql,qr,k,vis);
if(qr>mid) change_range(rson,ql,qr,k,vis);
update(p);
}
inline void Query(int l,int r,int p,int ql,int qr){
if(ql<=l&&qr>=r){
ans+=tree[p];
ans%=Mod;
return ;
}
if(tag_c[p]!=1){
downdate(l,r,p,tag_c[p],1);
}
if(tag_j[p]) downdate(l,r,p,tag_j[p],2);
int mid = (l+r)>>1;
if(ql<=mid) Query(lson,ql,qr);
if(qr>mid) Query(rson,ql,qr);
return ;
}
int main()
{
//std::ios::sync_with_stdio(0);
int n,m;
for(int i = 1;i <= Mn*4;i++) tag_c[i] = 1;
scanf("%d%d%lld",&n,&m,&Mod);
for(int i = 1;i <= n;i++)scanf("%lld",&a[i]);
build(1,n,1);
int q,x,y;
ll k;
while(m--){
scanf("%d",&q);
if(q == 3){
ans = 0;
scanf("%d%d",&x,&y);
Query(1,n,1,x,y);
ans%=Mod;
printf("%lld\n",ans);
}else{
scanf("%d%d%lld",&x,&y,&k);
k%=Mod;
change_range(1,n,1,x,y,k,q);
}
}
return 0;
}
//Without simplicity,will lose a lot;without rigor,will lose everything
凡事先问自己配不配