前缀和算法可以在 O(1) 的时间复杂度下查询一个集合中的一个连续子集中的所有元素之和。
一维前缀和:
一维前缀和可以在 O(1) 的时间复杂度下求序列的任意连续范围内的所有元素之和。
假设数组 a ,数组 s 为其前缀和数组 ,则 s[i] 表示 a[1]~a[i] 之和;
假设左端点 l ,右端点 r ,求 a[l]~a[r] 的和,只需求 s[r] - s[l-1] 。
代码模板如下:
for (int i = 1; i <= n; i++) {//预处理出前缀和数组
s[i] += s[i - 1];
while (m--) {//求m次任意区间的和
cin >> l >> r;
cout << s[r] - s[l - 1] << endl;
}
二维前缀和:
二维前缀和可以在 O(1) 的时间复杂度下求矩阵的任意子矩阵中的所有元素之和。
设数组 s ,s[i][j] 表示以 (1,1) 为左上角,(i , j) 为右下角的矩阵中的所有元素之和;
求以 (m , n) 为左上角,(p , q) 为右下角的矩阵中所有元素之和,只需求 s[p][q] - s[p][m-1] - s[n-1][q] + s[m-1][n-1]。
图示:
代码模板如下:
for (int i = 1; i <= n; i++)//构造前缀和
for (int j = 1; j <= m; j++) {
cin>>a[i][j];
s[i][j] = a[i][j] + s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1];
}
while (k--){//求k次求矩阵元素之和( (a,b)~(c,d) )
cin>>a>>b>>c>>d;
cout << s[c][d] - s[a - 1][d] - s[c][b - 1] + s[a - 1][b - 1] << endl;
}
例题1. 截断数组:
思路:
首先,求数组中一段元素之和,用前缀和算法时间复杂度为O(1);
将数组分为三段需要截断两处,如果我们枚举两处的位置,时间复杂度为O(n^2),总体时间复杂度也为O(n^2);
尝试只枚举一处;
假设数组内所有元素之和为sum,那么分为的三段每段中的元素之和为sum/3;
一重循环遍历所有数,假设指针指向i;
如果s[i-2](区间1取[1,i-2],区间3取[i,n])==sum/3,这时就找到了一段第一段所有元素之和等于sum/3的区间(记录下找到的满足所需的第一段的个数,cnt++);
此时再找出第三段所有元素之和也为sum/3的区间即满足了三段均为sum/3,即s[n]-s[i-1] == sum/3;
图示:
代码如下:
if(s[n]%3){cout<<"0";return 0;}
long long res=0;//答案可能为c(n,2),会爆int
for(int i=3,cnt=0;i<=n;i++){
if(s[i-2]==s[n]/3)cnt++;
if(s[n]-s[i-1]==s[n]/3)res+=cnt;
}