这里直接以luogu上的一道了例题为例:https://www.luogu.com.cn/problem/P1438
题目描述
维护一个数列{a[i]},支持两种操作:
1、1 L R K D:给出一个长度等于R-L+1的等差数列,首项为K,公差为D,并将它对应加到a[L]~a[R]的每一个数上。即:令a[L]=a[L]+K,a[L+1]=a[L+1]+K+D,
a[L+2]=a[L+2]+K+2D……a[R]=a[R]+K+(R-L)D。
2、2 P:询问序列的第P个数的值a[P]。
输入格式
第一行两个整数数n,m,表示数列长度和操作个数。
第二行n个整数,第i个数表示a[i](i=1,2,3…,n)。
接下来的m行,表示m个操作,有两种形式:
1 L R K D
2 P 字母意义见描述(L≤R)。
输出格式
对于每个询问,输出答案,每个答案占一行。
输入输出样例
5 2 1 2 3 4 5 1 2 4 1 2 2 3
6
说明/提示
数据规模:
0≤n,m≤100000
|a[i]|,|K|,|D|≤200
思路:
设原数组为a,考虑对a进行差分,对于差分数组d,a[p] = d[1] + d[2] + ... + d[p]
,而对a[l]~a[r]加上等差数列的值,在数组d上的修改为:d[l] = a[l] - a[l - 1],a[l]增加k,则d[l]增加了k
。而d[l + 1] = a[l + 1] - a[l],a[l + 1]增加k + d,a[l]增加k,即d[l + 1]增加d,同理对于i属于[l + 1, r],d[i]都增加了d
。最后d[r + 1] = a[r + 1] - a[r],a[r + 1]不变而a[r]增加了k + (r - l)d,故d[r + 1]增加了(l - r)d - k
。
到这里发现,对于a的操作,变换到d上,实际就成了区间求和/修改
,a的更新操作,等价于d[l]和d[r + 1]更新以及d[l + 1]~d[r]区间更新。而得到a[p]的操作,相当于d[1]到d[p]区间求和。
那么我们就很容易通过线段树来做了
1 #include <iostream> 2 #include <algorithm> 3 #include <string> 4 #include <string.h> 5 #include <vector> 6 #include <map> 7 #include <stack> 8 #include <set> 9 #include <queue> 10 #include <math.h> 11 #include <cstdio> 12 #include <iomanip> 13 #include <time.h> 14 15 #define LL long long 16 #define INF 0x3f3f3f3f 17 #define ls nod<<1 18 #define rs (nod<<1)+1 19 20 using namespace std; 21 22 const int maxn = 1e5 + 10; 23 const LL mod = 1e9 + 7; 24 25 26 struct segment_tree { 27 LL val; 28 LL lazy; 29 }tree[maxn<<2]; 30 31 LL a[maxn]; 32 33 void build (int l,int r,int nod) { 34 tree[nod].lazy = 0; 35 if (l == r) { 36 tree[nod].val = a[l]; 37 return ; 38 } 39 int mid = (l + r) >> 1; 40 build(l,mid,ls); 41 build(mid+1,r,rs); 42 tree[nod].val = tree[ls].val + tree[rs].val; 43 } 44 45 void pushdown(int l,int r,int nod) { 46 int mid = (l + r) >> 1; 47 tree[ls].lazy += tree[nod].lazy; 48 tree[rs].lazy += tree[nod].lazy; 49 tree[ls].val += (mid-l+1)*tree[nod].lazy; 50 tree[rs].val += (r-mid)*tree[nod].lazy; 51 tree[nod].lazy = 0; 52 } 53 54 void modify(int l,int r,int ql,int qr,int v,int nod) { 55 if (ql <= l && qr >= r) { 56 tree[nod].lazy += v; 57 tree[nod].val += (r-l+1)*v; 58 return ; 59 } 60 if (tree[nod].lazy) 61 pushdown(l,r,nod); 62 int mid = (l + r) >> 1; 63 if (ql <= mid) 64 modify(l,mid,ql,qr,v,ls); 65 if (qr > mid) 66 modify(mid+1,r,ql,qr,v,rs); 67 tree[nod].val = tree[ls].val + tree[rs].val; 68 } 69 70 LL query(int l,int r,int ql,int qr,int nod) { 71 if (ql <= l && qr >= r) 72 return tree[nod].val; 73 int mid = (l + r ) >> 1; 74 if (tree[nod].lazy) 75 pushdown(l,r,nod); 76 LL cnt = 0; 77 if (ql <= mid) 78 cnt +=query(l,mid,ql,qr,ls); 79 if (qr > mid) 80 cnt +=query(mid+1,r,ql,qr,rs); 81 return cnt; 82 } 83 LL dat[maxn]; 84 int main() { 85 int n,m; 86 scanf("%d%d",&n,&m); 87 for (int i = 1;i <= n;i++) { 88 scanf("%lld",&dat[i]); 89 } 90 for(int i=1;i<=n;i++) 91 a[i]=dat[i]-dat[i-1]; 92 build(1,n,1); 93 while (m--) { 94 int opt,l,r,k,d; 95 scanf("%d",&opt); 96 if (opt == 1) { 97 scanf("%d%d%d%d",&l,&r,&k,&d); 98 modify(1,n,l,l,k,1); 99 if (l+1 <= r) 100 modify(1,n,l+1,r,d,1); 101 if (r+1 <= n) 102 modify(1,n,r+1,r+1,-(k+d*(r-l)),1); 103 } 104 else { 105 scanf("%d",&l); 106 printf("%d ",query(1,n,1,l,1)); 107 } 108 } 109 return 0; 110 }
当然我们还可以进一步思考,进行数学推导
对d再次差分得到数组e:
e[1] = d[1] = a[1]
e[2] = d[2] - d[1] = (a[2] - a[1]) - a[1] = a[2] - 2a[1]
e[3] = d[3] - d[2] = (a[3] - a[2]) - (a[2] - a[1]) = a[3] - 2a[2] + a[1]
...
e[i] = d[i] - d[i - 1] = (a[i] - a[i - 1]) - (a[i - 1] - a[i - 2])
= a[i] - 2a[i - 1] + a[i - 2] ...
由之前的概述已知,对应到d为:
- d[l] 增加 k
- d[i] 增加 d (i属于[l + 1, r]),即
d[l + 1]~d[r]区间增加d
- d[r + 1] 增加 (l - r)*d - k
对应到e为: d[l]增加k导致e[l]增加k,e[l + 1]增加 -k
,
d[l + 1]~d[r]区间增加d,导致e[l + 1]增加d,e[r + 1]增加 -d,而e[l + 2]到e[r]这一段由于抵消而不变化
。
d[r + 1]增加(l - r)*d - k导致e[r + 1]增加(l - r)*d - k,e[r + 2]增加 k - (l - r)*d
综上,a的更新操作对应到e为:
- e[l] 增加 k
- e[l + 1]增加 d- k
- e[r + 1] 增加 (l - r - 1)*d - k
- e[r + 2] 增加 k - (l - r)*d
所以我们也可以通过二次差分数组再求两次前缀和就可以得到最后结果
算法模版:
#include<bits/stdc++.h> using namespace std; const int MAXN=100005; int n,m,d2[MAXN],l,r,a,k; void add(int l,int r,int a,int k) { d2[l]+=a; d2[l+1]+=k-a; d2[r+1]-=(r-l+1)*k+a; d2[r+2]-=(l-r)*k-a; } void pre_sum() { for(int i=1;i<=n;++i) { d2[i]+=d2[i-1]; } } int main() { scanf("%d %d",&n,&m); for(int i=1;i<=m;++i) { scanf("%d %d %d %d",&l,&r,&a,&k); add(l,r,a,k); } pre_sum(); pre_sum(); for(int i=1;i<=n;++i) { printf("%d%c",d2[i],i==n?' ':' '); } return 0; }
其实区间➕多项式的问题都可以转化为差分数组,然后对差分数组进行修改,最后再求前缀和从而解决
数学证明:https://blog.nowcoder.net/n/b0401b709aa540f0af78dfc8e66813fb
维护前缀和的前缀和
https://blog.nowcoder.net/n/bb352f0f59ea4509b0a7fc15b11fa5a8