【前缀和】
什么是前缀和?前缀和是一个数组的某项下标之前(包括此项元素)的所有数组元素的和。
设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 ",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 ", 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_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; }