【前綴和】
什么是前綴和?前綴和是一個數組的某項下標之前(包括此項元素)的所有數組元素的和。
設b[]為前綴和數組,a[]為原數組,根據這句話可以得到前綴和的定義式和遞推式:
定義式 | 遞推式 | |
一維前綴和 | ![]() |
![]() |
二維前綴和 | ![]() |
![]() |
【一維前綴和】
根據上面的定義,我們可以很容易得到 sum[i] = sum[i-1] + a[i] 這樣就可以得到前i個數的和
根據上述表達式我們可以以O(1)求出區間[i,j]的區間和
代碼:

#include <cstdio> #include <string> #include <iostream> #include <algorithm> #include <cstdbool> #include <string.h> #include <math.h> using namespace std; int main() { int a[100005] = {0}; int b[100005] = {0}; int n; cin >> n; for (int i=1;i<=n;i++) { cin >> a[i]; b[i] = b[i-1] + a[i]; } int t; cin >> t; while (t--) { int l,r; int sum = 0; cin >> l >> r; sum = b[r] - b[l-1]; printf("%d\n",sum); } return 0; }
這道題我改動了一下,如果可以找到,那么輸出所有的符合條件的區間
這道題的思路其實不難理解:
例如:
[1,a] 和 [1,b] (b > a)
如果要滿足條件那么 (b-a) % m == x
-> (b-x) % m == a
我們對它的前綴和對m模(假設為 t )並存儲
vis[(t-x+m)%m] 存在,則說明可以找到這樣的一個子區間
但是由於我這里要輸出所有的符合條件的區間,所以我需要去存儲數組索引 (也就是區間的左邊界)
一種是利用二維數組:

#include <cstdio> #include <string> #include <iostream> #include <algorithm> #include <cstdbool> #include <string.h> #include <math.h> using namespace std; int main() { int n,m,x; cin >> n >> m >> x; int ss[105][105]; for (int i=0;i<105;i++) { for (int j=0;j<105;j++) { ss[i][j] = -1; } } int a[105]; int vis[105] = {0}; int pre[105]; pre[0] = 0; ss[0][0] = 0; int l,r; for (int i=1;i<=n;i++) { cin >> a[i]; } for (int i=1;i<=n;i++){ pre[i] = (pre[i-1] + a[i]) % m; vis[pre[i]]++; int xh = (pre[i] - x) % m; if (xh < 0) xh += m; if (vis[xh] != 0) { for (int j=0;j<105;j++) { if (ss[xh][j] != -1) { l = ss[xh][j] + 1; r = i; cout << l << " " << r << endl; } else break; } } for (int j=0;j<105;j++) { if (ss[pre[i]][j] == -1) { ss[pre[i]][j] = i; break; } else continue; } } return 0; }
一種是利用vector:

#include <cstdio> #include <string> #include <iostream> #include <algorithm> #include <cstdbool> #include <string.h> #include <math.h> #include <vector> using namespace std; int main() { int n; int m; int x; scanf("%d%d%d", &n, &m, &x); int a[100]; for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); } int pre[100]; pre[0] = 0; vector<int> preffix[100]; preffix[0].push_back(0); for (int i = 1; i <= n; ++i) { pre[i] = (pre[i-1] + a[i]) % m; int xh = (pre[i] - x) % m; if (xh < 0) { xh += m; } if (!preffix[xh].empty()) { for (int pre_index: preffix[xh]) { printf("%d:%d\n", pre_index + 1, i); } } preffix[pre[i]].push_back(i); } return 0; }
直接在問題二的基礎上修改就可以了
思路:
因為是除去區間[l,r] 那么我就分兩個前綴和(一個從前往后求,一個從后往前求)
pre[l-1] 和 suf[r+1] 求這倆的gcd就可以了
代碼:

#include <cstdio> #include <string> #include <iostream> #include <algorithm> #include <cstdbool> #include <string.h> #include <math.h> #include <vector> using namespace std; int pre[10005]; int suf[10005]; int a[100005]; int n; int gcd(int a,int b) { return b ? gcd(b, a % b) : a; } void presolve() { pre[0] = 0; for(int i = 1; i <= n; i++) { pre[i] = gcd(pre[i - 1], a[i]); } suf[n + 1] = 0; for(int i = n; i >= 1; i--) { suf[i] = gcd(suf[i + 1], a[i]); } } int query(int l,int r) { return gcd(pre[l-1],suf[r+1]); } int main() { cin >> n; for (int i=1;i<=n;i++) cin >> a[i]; presolve(); int l,r; cin >> l >> r; int n_gcd = query(l,r); printf("%d\n",n_gcd); return 0; }
問題【五】
給你一串長度為n的數列a1,a2,a3......an,要求對a[L]~a[R]進行m次操作:
操作一:將a[L]~a[R]內的元素都加上P
操作二:將a[L]~a[R]內的元素都減去P
最后再給出一個詢問求a[L]-a[R]內的元素之和?
這里講差分!
它可以用來專門解決這種修改值的問題!

#include<bits/stdc++.h> using namespace std; const int maxn=1e5+9; int a[maxn],b[maxn]; int main(){ int i,j,k,n,m,p; cin>>n>>m; for(i=1;i<=n;i++){ cin>>a[i]; } for(i=1;i<=m;i++){ int L,R,t; cin>>t>>L>>R>>p; if(t==1){ b[L]+=p;b[R+1]-=p; //仔細想想為什么b[R+1]要減去p } else{ b[L]-=p;b[R+1]+=p; } } int add=0; for(i=1;i<=n;i++){ add+=b[i]; a[i]+=a[i-1]+add; } int x,y; cin>>x>>y; cout<<a[y]-a[x-1]<<endl; }
為什么操作一時b[R+1]要減去p,很簡單,因為操作一我只需對[L,R]區間里的數加p,[R+1,n]這個區間里的數沒必要加p,所以需要減掉p。
【二維前綴和】
DP[i][j]表示(1,1)這個點與(i,j)這個點兩個點分別為左上角和右下角所組成的矩陣內的數的和,好好想一下狀態轉移方程,DP[i][j]=DP[i-1][j]+DP[i][j-1]-DP[i-1][j-1]+map[i][j],怎么來的呢?我們畫一下圖就知道了。
這張圖就知道了(i,j)可以由(i-1,j)和(i,j-1)兩塊構成,不過要注意兩個點,1、有一塊矩陣我們重復加了,也就是(i-1,j-1)這一塊,所以我們要減去它。2、我們這個矩陣是不完整的,由圖可知我們還有一塊深藍色的沒有加,也就是(i,j)這一點,所以我們要再加上map[i][j]也就是題目給出的矩陣中這一格的數。
如果我們定義[x1,y1] 為所求矩陣左上角 [x2,y2] 為所求矩陣右下角 如何得到它們所圍成矩陣的總和呢?
我們可以通過DP[x2][y2]來計算,我們通過圖可以發現這個距離我們要的還差紅色的部分看看怎么表示紅色部分?我們可以分割成兩塊,分別是DP[x1][y2]和DP[x2][y1]我們發現有一塊重復減了,所以我們再加上它即DP[x1][y1],有一點注意,因為畫圖和定義原因我們發現邊界好像不對,我們來看看,我們定義的狀態是整個矩陣包括邊的和,而我們要求的也是要包括邊的,所以我們要再改一下,把DP[x1][y2]和DP[x2][y1]和DP[x1][y1]分別改成DP[x1-1][y2]和DP[x2][y1-1]和DP[x1-1][y1-1]這樣一減我們就可以得到自己想要的答案,整理可得公式,DP[x2][y2]-DP[x1-1][y2]-DP[x2][y1-1]+DP[x1-1][y1-1]這樣我們就可以做到O(1)之內查詢
求前綴和的代碼:

#include<iostream> #include<cstring> using namespace std; int dp[2000][2000],map[2000][2000]; int main() { int m,n,k;//所給的矩陣是n*m的,有k組查詢 cin >>n>>m>>k; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin >>map[i][j]; memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++)//預處理一波 for(int j=1;j<=m;j++) dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+map[i][j]; for(int i=1;i<=k;i++)//接受查詢 { int x1,x2,y1,y2; cin >>x1>>y1>>x2>>y2; cout <<(dp[x2][y2]+dp[x1-1][y1-1]-dp[x1-1][y2]-dp[x2][y1-1])<<endl;//O(1)查詢 } return 0; }