【前綴和】ios
什麼是前綴和?前綴和是一個數組的某項下標以前(包括此項元素)的全部數組元素的和。 c++
設b[]爲前綴和數組,a[]爲原數組,根據這句話能夠獲得前綴和的定義式和遞推式:數組
定義式 | 遞推式 | |
一維前綴和 | ||
二維前綴和 |
【一維前綴和】ide
根據上面的定義,咱們能夠很容易獲得 sum[i] = sum[i-1] + a[i] 這樣就能夠獲得前i個數的和spa
根據上述表達式咱們能夠以O(1)求出區間[i,j]的區間和 3d
代碼:code
#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; }
這道題我改動了一下,若是能夠找到,那麼輸出全部的符合條件的區間blog
這道題的思路其實不難理解: 索引
例如:ci
[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)兩塊構成,不過要注意兩個點,一、有一塊矩陣咱們重複加了,也就是(i-1,j-1)這一塊,因此咱們要減去它。二、咱們這個矩陣是不完整的,由圖可知咱們還有一塊深藍色的沒有加,也就是(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; }