题解 P3372 【【模板】线段树 1】

· · 题解

注:本文章中 \div 均代表向下取整的整数除法。

线段树的各种操作别的题解已经讲的很多了,我在这里将讲一种特殊的给区间开点的方法。

请看下面一段代码:

inline int ID(int l,int r){return (l+r) | (l!=r);}

这是啥玄学操作。

等等,这种开点方式有什么好的,区间之间的标号难道不会冲突吗?

答:这样开点,你的线段树只要开两倍空间,并且不会冲突

为什么呢?

证明如下:

f(x,y)=x+y

考虑一个根节点,它所代表的区间为 [l,r]
那它的左儿子代表的区间为 [l,(l+r)\div2]
右儿子代表的区间为为 [(l+r)\div2+1,r]

我们发现:
这个根节点的右子树的所有节点的 f 值肯定不会等于 f(l,r) ,因为:

f(l,r) 为偶数时,右子树中所有节点的最小 f 值为:

**当 $f(l,r)$ 为奇数时**,右子树中所有节点的最小 $f$ 值为: $(l+r)\div2+1 + (l+r)\div2+1 =l+r+1 ≠ f(l,r) $ 。 ------------ 同样的,我们讨论左子树: **当 $f(l,r)$ 为偶数时**,左子树中所有节点的最大 $f$ 值为: $(l+r)\div2 + (l+r)\div2 = l+r =f(l,r) $ 。 **当 $f(l,r)$ 为奇数时**,左子树中所有节点的最大 $f$ 值为: $(l+r)\div2 + (l+r)\div2 =l+r-1 ≠ f(l,r) $ 。 综上,只有当 $f(l,r)$ 为偶数时,会与左子树的一个节点(且一定是叶子结点)的 $f$ 值相同。 所以,对于一个非叶子结点: 若 $l+r$ 为偶数,则它的标号为 $l+r+1$ 。 若 $l+r$ 为奇数,则它的标号为 $l+r$ 。 这样就能保证所有的点的标号都不产生冲突了。 又因为任意一个非叶子结点的 $f$ 值一定小于所有它右子树的 $f$ 值。 所以,整棵树中所有点最大的标号为 $n+n=2n$ 。 因此,线段树的空间只要开**两倍**即可。 最后附上本题 AC 代码,各位读者可以结合代码来学会如何运用这样的开点方法: ```cpp #include<bits/stdc++.h> #define ll long long #define N 100005 using namespace std; int n,m; ll a[N]; struct data { ll sum,lazy; data(){sum=lazy=0;} }tree[N<<1]; data operator + (const data &x ,const data &y) { data ret; ret.sum=x.sum+y.sum; return ret; } inline int ID(int l,int r){return (l+r) | (l!=r);} void push_down(int k,int l,int r) { int mid=l+r>>1,lson=ID(l,mid),rson=ID(mid+1,r); ll Lazy=tree[k].lazy; tree[lson].sum+=Lazy*(mid-l+1); tree[lson].lazy+=Lazy; tree[rson].sum+=Lazy*(r-mid); tree[rson].lazy+=Lazy; tree[k].lazy=0; } void build(int k,int l,int r) { if(l==r) { tree[k].sum=a[l]; tree[k].lazy=0; return; } int mid=l+r>>1; int lson=ID(l,mid); int rson=ID(mid+1,r); build(lson,l,mid); build(rson,mid+1,r); tree[k]=tree[lson]+tree[rson]; } void update(int k,int l,int r,int L,int R,ll v) { if(L<=l&&r<=R) { tree[k].lazy+=v; tree[k].sum+=v*(r-l+1); return; } if(r<L||l>R)return; int mid=l+r>>1; push_down(k,l,r); int lson=ID(l,mid),rson=ID(mid+1,r); update(lson,l,mid,L,R,v); update(rson,mid+1,r,L,R,v); tree[k]=tree[lson]+tree[rson]; } ll query(int k,int l,int r,int L,int R) { if(L<=l&&r<=R)return tree[k].sum; if(r<L||l>R)return 0; int mid=l+r>>1; push_down(k,l,r); int lson=ID(l,mid),rson=ID(mid+1,r); return query(lson,l,mid,L,R)+query(rson,mid+1,r,L,R); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",a+i); build(ID(1,n),1,n);//线段树建树函数 build 的调用处传参有误,现已修正。 while(m--) { int op,l,r; ll v; scanf("%d%d%d",&op,&l,&r); if(op==1) { scanf("%lld",&v); update(ID(1,n),1,n,l,r,v); } else { printf("%lld\n",query(ID(1,n),1,n,l,r)); } } return 0; } ``` update 2019.7.27: 在管理员以及巨佬 @alpha1022 的提醒下,将文章的排版更加整齐化。在此感谢他们。 update 2019.7.31: 文章内容中有一处小错误,现已更正。 update 2025.1.22: 线段树建树函数 build 的调用处传参有误,现已修正。优化了文章排版,使其符合现在洛谷题解标准。