众所周知,线段树可以在O( log n)的时间内进行很多修改和查询的操作,应用很广。
线段树,顾名思义,是一个二叉树,但是每个节点,存的不是不是“数”,而是一个“区间”,在百度百科中有非常容易理解的图片,一看就能理解线段树是怎么存在的。
线段树的存储方式和树的相似。
例如:有10 个数,那么,根节点的sum就是这10个数的和,lazy标记表示这十个数都要加上一个数,那么根节点的左二子就存的1—5的sum,lazy只是1—5范围的,右儿子表示6—10的,左二子的左二子是1—3,左二子的右儿子是2—5,右儿子的……以此类推,一直拆分到每个节点表示一个数的为止。
这道模板题主要用到了区间修改、单点修改和区间查询、单点查询(为了偷懒,我将单点修改、查询写成了查询、修改长度为 1 的区间……sorry)。
实现线段树的区间操作需要用到懒标记 lazy 数组,涉及到懒标记的下放(pushdown)和上传(pushup);
题意详见 Luogu 2357 守墓人;
下面在代码中对线段树进行分析:
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<string> 5 #include<cmath> 6 #include<algorithm> 7 using namespace std; 8 9 long long num[1000000];
//num数组存的是该节点的区间有多少个数,按理说这个是用不到的,
//但我菜 QAQ~ ,迫不得已又开了这个数组,下面会说…… 10 long long a[1000000],sum[1000000],n,f,lazy[1000000],m,l,r,k;// 11 long long ans;
//a数组存的是该节点的数……
//sum存的线段树的节点表示的区间的值的和。
//lazy是懒标记,标记这个区间要加的数,因为“懒”,所以用不到时就一边待着…… 12 13 void pushdown(int o){ 14 lazy[o*2+1]+=lazy[o]; 15 lazy[o*2]+=lazy[o]; 16 lazy[o]=0; 17 return; 18 } 19
//下放函数,将该节点的懒标记,下放到左孩子和右孩子,自己的清零。
20 void pushup(int o){ 21 if(o==1) return; 22 int p=o/2; 23 sum[p]=sum[p*2]+sum[p*2+1]+lazy[p*2]*num[p*2]+lazy[p*2+1]*num[p*2+1]; 24 pushup(o/2); 25 } 26
//上传函数,因为子节点值出现修改,就沿着树向上修改父亲的值,一直修改到根节点。
27 long long build(int l,int r,int o){ 28 if(l>r) return 0; 29 if(l==r){ 30 num[o]=1; 31 sum[o]=a[l]; 32 return sum[o]; //如果拆分到不能再分,就把改点作为一个叶子节点,并返回值 33 }else{ 34 num[o]=r-l+1; //区间长度就是这个区间存在的数的个数。 35 sum[o]=build(l,(l+r)/2,o*2)+build((l+r)/2+1,r,o*2+1); 36 return sum[o]; //如果改点不是叶子节点,那么他的和就是左二子的+右儿子的。 37 } 38 } 39 40 void addn(int x,int y,int k,int o,int l,int r){ //加值函数 41 int mid=(l+r)/2; 42 if(x==l&&y==r){ //如果要修改的恰好为一个区间,就直接在区间上操作 43 lazy[o]+=k; //懒标记记录整个区间每个数要加的个数 44 sum[o]+=lazy[o]*(r-l+1); //sum记录整个区间加完数之后的值 45 pushdown(o); //下放标记 46 pushup(o); //上传值 47 return; 48 }else 49 if(x>=mid+1&&y>=mid+1){ //如果要操作的都在区间中点的右边(右孩子),就到右孩子操作 50 addn(x,y,k,o*2+1,mid+1,r); 51 }else 52 if(x<=mid&&y<=mid){ //如果要操作的都在区间中点的左边(左孩子),就到左孩子操作 53 addn(x,y,k,o*2,l,mid); 54 }else 55 if(x<=mid&&y>=mid+1){ //如果该区间横跨左右两个孩子,就将该区间从中间劈开,分别处理 56 addn(x,mid,k,o*2,l,mid); 57 addn(mid+1,y,k,o*2+1,mid+1,r); 58 } 59 } 60 61 long long qiuh(int x,int y,int o,int l,int r){ //求和函数,原谅我不会 求和 的英语TUT 62 int mid=(l+r)/2; 63 if(x==l&&y==r) return (sum[o]+lazy[o]*(r-l+1)); 64 else 65 if(x>=mid+1&&y>=mid+1){ 66 sum[o]+=lazy[o]*(r-l+1); 67 pushdown(o); 68 return (qiuh(x,y,o*2+1,mid+1,r)); 69 } 70 else 71 if(x<=mid&&y<=mid){ 72 sum[o]+=lazy[o]*(r-l+1); 73 pushdown(o); 74 return (qiuh(x,y,o*2,l,mid)); 75 } 76 else 77 if(x<=mid&&y>=mid+1){ 78 sum[o]+=lazy[o]*(r-l+1); 79 pushdown(o); 80 return qiuh(x,mid,o*2,l,mid)+qiuh(mid+1,y,o*2+1,mid+1,r); 81 } 82 } 83 //求和函数的操作方式与加值函数几乎相同,看懂价值函数后很容易理解求和函数…… 84 int main(){ 85 scanf("%ld %ld",&n,&f); 86 for(int i=1;i<=n;i++) scanf("%ld",&a[i]); 87 build(1,n,1); 88 for(int i=1;i<=f;i++){ 89 scanf("%ld",&m); 90 switch (m){ 91 case 1:{ 92 scanf("%ld %ld %ld",&l,&r,&k); 93 addn(l,r,k,1,1,n); 94 break; 95 } 96 case 2:{ 97 scanf("%ld",&k); 98 addn(1,1,k,1,1,n); 99 break; 100 } 101 case 3:{ 102 scanf("%ld",&k); 103 addn(1,1,k*(-1),1,1,n); 104 break; 105 } 106 case 4:{ 107 scanf("%ld %ld",&l,&r); 108 ans=qiuh(l,r,1,1,n); 109 printf("%ld ",ans); 110 break; 111 } 112 case 5:{ 113 ans=qiuh(1,1,1,1,n); 114 printf("%ld ",ans); 115 break; 116 } 117 } 118 } 119 return 0; 120 }