前綴和是一種很重要的思想 其實這個思想很簡單html
就是一次預處理 把原來O(N*M)的複雜度降到O(N+M)
c++
∑a[i]+...+a[j]=sum[j]-sum[i-1];git
#include <bits/stdc++.h> using namespace std; typedef long long ll; inline ll read() { ll x=0,f=1; char ch=getchar(); while(!isdigit(ch)) (ch=='-')?(f=-1,ch=getchar()):ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x*f; } int n; const int N=1<<20; int a[N],sum[N]; signed main(){ memset(sum,0,sizeof(sum));//先清空數組 n=read(); for(register int i=1;i<=n;i++) a[i]=read();//讀入 for(register int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];//前綴和 }
那麼區間的數值查詢該怎麼作呢數組
假設有m次查詢ide
for(register int i=1;i<=m;i++) { int x=read(),y=read(); ans=sum[y]-sum[x-1]; }
ans=∑a[x]+...a[y];
spa
如上所述code
∑a[x]+...a[y]=sum[y]-sum[x-1];htm
想一想看爲何要 -1 而不是 sum[y]-sum[x]blog
舉個例子:ip
n=5
i | 1 | 2 | 3 | 4 | 5 |
a[i] | 5 | 3 | 4 | 2 | 1 |
sum[i] | 5 | 8 | 12 | 14 | 15 |
看例子 本身體會吧...(我太菜了講不明白)
接下來來幾道例題
Problem
給出一個含有n個整數的數列a,而且有m次詢問,每次詢問數列在區間[l,r][l,r]內的和,即求a[l]+a[l+1]+…+a[r]的值。
Input Data
第一行爲一個整數 T (1≤T≤50),表示共有T組輸入數據;
對於每組數據,第一行是兩個正整數 n,m (1≤n≤100000 ,1≤m≤1000)分別表明數列長度和詢問次數;
第二行有 n 個正整數,第 i 個數表示數列元素 a[i](1≤a[i]≤109)的值;
接下來 m 行,每行有兩個正整數 l,r (l≤r≤n),表明詢問內容。
Output Data
每組數據輸出 m 行,每行一個數爲該次詢問的區間和。
保證數據都在64位正整數範圍內。
裸題
#include<bits/stdc++.h> #define ULL unsigned long long #define MAXN 100000+5 #define f(i,j,n) for(register int i=j;i<=n;i++) using namespace std; ULL T,a[MAXN],n,m; void solve(int T) { while(T--) { memset(a,0,sizeof(a)); cin>>n>>m; f(i,1,n) { int x; cin>>x; a[i]=a[i-1]+x; } f(i,1,m) { int l,r; cin>>l>>r; cout<<a[r]-a[l-1]<<endl; } } } int main() { cin>>T; solve(T); return 0; }
Problem
給定n個整數構成的序列,全部相鄰的m個數有n−m+1個,求其中的最小值。
Input Data
第一行,2個整數n和m,範圍在[3…100000],n>m;
第二行,有n個正整數,範圍在[3…1000]。
Output Data
一個整數,表示最小值。
這題是經過預處理以後再進行枚舉(也是前綴和的一般作法)
#include<bits/stdc++.h> #define ULL unsigned long long #define MAXN 100000+5 #define f(i,j,n) for(register int i=j;i<=n;i++) #define INF 2147483647 using namespace std; ULL a[MAXN],n,m,MIN=INF; void solve(int T) { while(T--) { memset(a,0,sizeof(a)); cin>>n>>m; f(i,1,n) { int x; cin>>x; a[i]=a[i-1]+x; } f(i,m,n) MIN=min(MIN,a[i]-a[i-m]); cout<<MIN<<endl; } } int main() { solve(1); return 0; }
在一個n∗m的只包含0和1的矩陣裏找出一個不包含0的最大正方形,輸出邊長。
Input Data
第一行爲兩個整數n,m(1≤n,m≤100),接下來n行,每行m個數字,用空格隔開,0或1。
Output Data
一個整數,最大正方形的邊長
#include<bits/stdc++.h> #define ULL unsigned long long #define MAXN 100+5 #define f(i,j,n) for(register int i=j;i<=n;i++) using namespace std; ULL T,a[MAXN][MAXN],n,m; int q(int i,int j,int k,int l) { f(p,i,j) f(q,k,l) if(a[p][q]==0) return 0; return min(j-i+1,l-k+1); } void solve(int T) { int ans=0; while(T--) { memset(a,0,sizeof(a)); cin>>n>>m; f(i,1,n) f(j,1,m) { int x; cin>>x; a[i][j]=x; } f(i,1,n) f(j,i,n) f(k,1,m) f(l,k,m) ans=max(ans,q(i,j,k,l)); cout<<ans<<endl; } } int main() { solve(1); return 0; }
拓展(二維前綴和)
#include<bits/stdc++.h> #define f(i,j,n) for(register int i=j;i<=n;i++) using namespace std; const int N=100; int n,m; int a[N][N]; int main() { scanf("%d",&n,&m); f(i,1,n) f(j,1,m) { int x; scanf("%d", &x); a[i][j]=x+a[i-1][j]+a[i][j-1]-a[i-1][j-1]; } int x1, y1, x2, y2; while(~scanf("%d%d%d%d",&x1,&y1,&x2,&y2)) printf("%d\n",a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1]); return 0; }
BZOJ1218
HNOI2003
Problem
一種新型的激光炸彈,能夠摧毀一個邊長爲R的正方形內的全部的目標。
如今地圖上有n(n≤10000)個目標,用整數Xi,Yi(其值在[0,5000][0,5000])表示目標在地圖上的位置,每一個目標都有一個價值。激光炸彈的投放是經過衛星定位的,但其有一個缺點,就是其爆破範圍,即那個邊長爲R的正方形的邊必須和x,y軸平行。若目標位於爆破正方形的邊上,該目標將不會被摧毀。
Input Data
輸入文件的第一行爲正整數n和正整數R,
接下來的n行每行有3個正整數,分別表示xi,yi,vi
Output Data
輸出文件僅有一個正整數,表示一顆炸彈最多能炸掉地圖上總價值爲多少的目標(結果不會超過32767)。
#include <bits/stdc++.h> #define f(i,j,n) for(register int i=j;i<=n;i++) #define M 5010 using namespace std; int n,r,ans,sum[M][M]; int main() { int x,y,z; cin>>n>>r; f(i,1,n) scanf("%d%d%d",&x,&y,&z),sum[x+1][y+1]+=z; f(i,1,5000) f(j,1,5000) sum[i][j]+=sum[i-1][j]; f(i,1,5000) f(j,1,5000) sum[i][j]+=sum[i][j-1]; f(i,r,5000) f(j,r,5000) ans=max(ans,sum[i][j]+sum[i-r][j-r]-sum[i][j-r]-sum[i-r][j]); cout<<ans<<endl; return 0; }
(代碼都是很久之前寫的了 碼風可能不太好 見諒)