這里直接以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\n",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?'\n':' '); } return 0; }
其實區間➕多項式的問題都可以轉化為差分數組,然后對差分數組進行修改,最后再求前綴和從而解決
數學證明:https://blog.nowcoder.net/n/b0401b709aa540f0af78dfc8e66813fb
維護前綴和的前綴和
https://blog.nowcoder.net/n/bb352f0f59ea4509b0a7fc15b11fa5a8