线段树(写法剖析和一些细节)
#include<bits/stdc++.h>
#define lson root<<1
#define rson root<<1|1
typedef long long LL ;
using namespace std;
const int MAXN = 1e5+1 ;
struct SgT{
int l, r ;
LL add ; // the plus lazy tag
LL sum ;
}T[MAXN<<2] ;
int n, m ;
void Merge(int root){
T[root].sum = T[lson].sum + T[rson].sum ;
}
void TagPushDown(int root){
if(T[root].add == 0) return ;
// to cutdown the const ......
int len = T[root].r - T[root].l + 1 ;
T[lson].sum += (len - (len>>1) ) * T[root].add ;
// the lson is bigger sometimes
T[rson].sum += (len>>1) * T[root].add ;
T[lson].add += T[root].add ;
T[rson].add += T[root].add ;
T[root].add = 0 ;
}
void Build(int l, int r, int root){
T[root].l = l ;
T[root].r = r ;
if(l == r){
scanf("%lld", &T[root].sum) ;
return ;
}
int mid = (l+r)>>1 ;
Build(l, mid, lson) ; // left-son-tree
Build(mid+1, r, rson) ; //right-son-tree
Merge(root) ;
}
void Update(int L, int R, int root, LL key){
// you want to update [L,R]
// totally included
if(T[root].l >= L && T[root].r <= R){
T[root].add += key ;
T[root].sum += (T[root].r - T[root].l + 1) * key ;
return ;
}
// not included in [L,R]
// put down the lazy tag
TagPushDown(root) ;
// the initial recursion part
// if(T[lson].r >= L && T[lson].l <= R) Update(L, R, lson, key) ;
// if(T[rson].l <= R && T[rson].r >= L) Update(L, R, rson, key) ;
// after thinking about it, I choose the following code
int mid = (T[root].l + T[root].r)>>1 ;
if(mid >= L) Update(L, R, lson, key) ;
if(mid < R) Update(L, R ,rson, key) ;
// take the lson for example
// we don't need the T[lson].r <= R
// because the second line promises the situation doesn't exist
Merge(root) ;
}
LL Query(int L, int R, int root){
// you want to get the sum
if(T[root].l >= L && T[root].r <= R){
return T[root].sum ;
}
// no matter update or query
// if you visit the smaller region
// you have to put down your lazy tag
TagPushDown(root) ;
LL re=0 ;
int mid = (T[root].l + T[root].r)>>1 ;
if(mid >= L) re += Query(L, R, lson) ;
if(mid < R) re += Query(L, R, rson) ;
// no update, no merge
return re ;
}
int main(){
freopen("in.txt", "r", stdin) ;
scanf("%d%d", &n, &m) ;
Build(1, n, 1) ;
int x, y, flag ;
LL k ;
for(int i=1 ; i<=m ; i++ ){
scanf("%d%d%d", &flag, &x, &y) ;
if(flag == 1){
scanf("%lld", &k) ;
Update(x, y, 1, k) ;
}
else {
printf("%lld\n", Query(x, y, 1) ) ;
}
}
return 0 ;
}
不对算法思想进行解说,只分析一下代码实现过程中的细节与思考。
void Update(int L, int R, int root, LL key){
if(T[root].l >= L && T[root].r <= R){
T[root].add += key ;
T[root].sum += (T[root].r - T[root].l + 1) * key ;
return ;
}
TagPushDown(root) ;
int mid = (T[root].l + T[root].r)>>1 ;
if(mid >= L) Update(L, R, lson, key) ;
if(mid < R) Update(L, R ,rson, key) ;
Merge(root) ;
}
如果当前的区间被完全包含在了访问的区间中,我们就打上lazy标记,同时不在往下递归。如果没有被完全包含,那么我们的标记要传下去。无论访问是否包含两个子区间,因为有不包含整个大区间的修改,当前的大区间已经能包含两个子区间的信息。
关于递归部分,代码经过了一些修改。
一开始是写成这样的:
if(T[lson].r >= L && T[lson].l <= R) Update(L, R, lson, key) ;
if(T[rson].l <= R && T[rson].r >= L) Update(L, R, rson, key) ;
这样写起来感觉挺符合思路的。但是分析之后,发现有冗余的判断。
观察左子区间,会不会出现左区间的左端点,就是本区间的左端点,还在查询区间的右边的情况?
如果出现这样的情况,说明在上一级的区间中,本区间作为右子区间进入了递归。但是观察到第二条语句的条件,这样的情况不可能发生。所以这条判断可以去掉。同理,右子区间中的判断也可以删掉。
改成了这个样子:
if(T[lson].r >= L) Update(L, R, lson, key) ;
if(T[rson].l <= R) Update(L, R, rson, key) ;
感觉可能常数会大,又改成了这个样子:
int mid = (T[root].l + T[root].r)>>1 ;
if(mid >= L) Update(L, R, lson, key) ;
if(mid < R) Update(L, R ,rson, key) ;
标记传递是写成这个样子的:
void TagPushDown(int root){
if(T[root].add == 0) return ;
// to cutdown the const ......
int len = T[root].r - T[root].l + 1 ;
T[lson].sum += (len - (len>>1) ) * T[root].add ;
// the lson is bigger sometimes
T[rson].sum += (len>>1) * T[root].add ;
T[lson].add += T[root].add ;
T[rson].add += T[root].add ;
T[root].add = 0 ;
}
左子区间长度总是不小于右子区间的(有时相等,有时大1),所以长度可以写成(len - (len>>1) )。