PrevNext
Not Frequent
 0/12

More on Prefix Sums

Authors: Darren Yao, Neo Wang, Qi Wang, Mihnea Brebenel

Contributors: Jesse Choe, Kevin Sheng, Brad Ma, Juheon Rhee

Max subarray sum, prefix sums in two dimensions, and a more complicated example.

Max Subarray Sum

Focus Problem – try your best to solve this problem before continuing!

Solution - Max Subarray Sum

Consider the prefix sum array pp. The subarray sum aiaj1a_i \dots a_{j-1}, where i<ji < j is p[j]p[i]p[j]-p[i]. Thus, we are looking for the maximum possible value of p[j]p[i]p[j]-p[i] over 0i<jN0 \leq i < j \leq N.

For a fixed right bound jj, the maximum subarray sum is

p[j]mini<jp[i]p[j]-\min_{i < j}{p[i]}

Thus, we can keep a running minimum to store mini<jp[i]\min\limits_{i < j}{p[i]} as we iterate through jj. This yields the maximum subarray sum for each possible right bound, and the maximum over all these values is our answer.

Implementation

C++

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
int main() {
int n;
cin >> n;

Java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
public class MaxSubSum {
public static void main(String[] args) throws IOException {
BufferedReader read = new BufferedReader(new InputStreamReader(System.in));
read.readLine();
int[] arr = Arrays.stream(read.readLine().split(" ")

Python

size = int(input())
arr = [int(i) for i in input().split()]
assert len(arr) == size
max_subarray_sum = arr[0]
min_pref_sum = 0
running_pref_sum = 0
for i in arr:
running_pref_sum += i
max_subarray_sum = max(max_subarray_sum, running_pref_sum - min_pref_sum)
min_pref_sum = min(min_pref_sum, running_pref_sum)
print(max_subarray_sum)

Alternative Solution - Kadane's Algorithm

2D Prefix Sums

Focus Problem – try your best to solve this problem before continuing!

Now, what if we wanted to process QQ queries for the sum over a subrectangle of a 2D matrix with NN rows and MM columns? Let's assume both rows and columns are 1-indexed, and we use the following matrix as an example:

0 0 0 0 0 0
0 1 5 6 11 8
0 1 7 11 9 4
0 4 6 1 3 2
0 7 5 4 2 3

Naively, each sum query would then take O(NM)\mathcal{O}(NM) time, for a total of O(QNM)\mathcal{O}(QNM). This is too slow.

Let's take the following example region, which we want to sum:

0 0 0 0 0 0
0 1 5 6 11 8
0 1 7 11 9 4
0 4 6 1 3 2
0 7 5 4 2 3

Manually summing all the cells, we have a submatrix sum of 7+11+9+6+1+3=377+11+9+6+1+3 = 37.

The first logical optimization would be to do one-dimensional prefix sums of each row. Then, we'd have the following row-prefix sum matrix. The desired subarray sum of each row in our desired region is simply the green cell minus the red cell in that respective row. We do this for each row to get (281)+(144)=37(28-1) + (14-4) = 37.

0 0 0 0 0 0
0 1 6 12 23 31
0 1 8 19 28 32
0 4 10 11 14 16
0 7 12 16 18 21

Now, if we wanted to find a submatrix sum, we could break up the submatrix into a subarray for each row, and then add their sums, which would be calculated using the prefix sums method described earlier. Since the matrix has NN rows, the time complexity of this is O(QN)\mathcal{O}(QN). This might be fast enough for Q=105Q=10^5 and N=103N=10^3, but we can do better.

In fact, we can do two-dimensional prefix sums. In our two dimensional prefix sum array, we have

prefix[a][b]=i=1aj=1barr[i][j].\texttt{prefix}[a][b]=\sum_{i=1}^{a} \sum_{j=1}^{b} \texttt{arr}[i][j].

This can be calculated as follows for row index 1in1 \leq i \leq n and column index 1jm1 \leq j \leq m:

prefix[i][j]=prefix[i1][j]+prefix[i][j1]prefix[i1][j1]+arr[i][j]\begin{aligned} \texttt{prefix}[i][j] =& \, \texttt{prefix}[i-1][j]+ \texttt{prefix}[i][j-1] \\ &- \texttt{prefix}[i-1][j-1]+ \texttt{arr}[i][j] \end{aligned}

Let's calculate prefix[2][3]\texttt{prefix}[2][3]. Try playing with the interactive widget below by clicking the buttons to see which numbers are added in each step. Notice how we overcount a subrectangle, and how we fix this by subtracting prefix[i1][j1]\texttt{prefix}[i-1][j-1].

add prefix[i-1][j]
add prefix[i][j-1]
subtract prefix[i-1][j-1]
add array[i][j]
to get prefix[i][j]
000000
0156118
0171194
046132
075423

The submatrix sum between rows aa and AA and columns bb and BB, can thus be expressed as follows:

i=aAj=bBarr[i][j]=prefix[A][B]prefix[a1][B]prefix[A][b1]+prefix[a1][b1]\begin{aligned} \sum_{i=a}^{A} \sum_{j=b}^{B} \texttt{arr}[i][j]=&\,\texttt{prefix}[A][B] - \texttt{prefix}[a-1][B] \\ &- \texttt{prefix}[A][b-1] + \texttt{prefix}[a-1][b-1] \end{aligned}

Summing the blue region from above using the 2D prefix sums method, we add the value of the green square, subtract the values of the red squares, and then add the value of the gray square. In this example, we have

65236+1=37,65-23-6+1 = 37,

as expected.

0 0 0 0 0 0
0 1 6 12 23 31
0 2 14 31 51 63
0 6 24 42 65 79
0 13 36 58 83 100

Try playing with the interactive widget below by clicking the buttons to see which numbers are added in each step.

add prefix[A][B]
subtract prefix[a-1][B]
subtract prefix[A][b-1]
add prefix[a-1][b-1]
to get our result
000000
0156118
0171194
046132
075423

Since no matter the size of the submatrix we are summing, we only need to access four values of the 2D prefix sum array, this runs in O(1)\mathcal{O}(1) per query after an O(NM)\mathcal{O}(NM) preprocessing.

Solution - Forest Queries

Warning!

We need to be cautious of off-by-one errors, as intervals can be inclusive, exclusive, 1-indexed, etc.

C++

#include <iostream>
#include <vector>
using namespace std;
constexpr int MAX_SIDE = 1000;
int tree_pref[MAX_SIDE + 1][MAX_SIDE + 1];
int forest[MAX_SIDE + 1][MAX_SIDE + 1];
int main() {

Java

import java.io.*;
import java.util.StringTokenizer;
public class ForestQueries {
static int N;
static int Q;
static int[][] pfx;
static int[][] arr;
public static void main(String[] args) {
Kattio io = new Kattio();

Python

side_len, query_num = [int(i) for i in input().split()]
tree_prefixes = [[0 for _ in range(side_len + 1)] for _ in range(side_len + 1)]
for r in range(side_len):
for ci, c in enumerate(input()):
tree = c == "*"
tree_prefixes[r + 1][ci + 1] += (
tree_prefixes[r][ci + 1]
+ tree_prefixes[r + 1][ci]
- tree_prefixes[r][ci]
+ tree

Problems

StatusSourceProblem NameDifficultyTags
SilverEasy
Show Tags2D Prefix Sums
Old SilverEasy
Show Tags2D Prefix Sums
CFNormal
Show TagsPrefix Sums
SilverHard
Show Tags2D Prefix Sums
GoldHard
Show Tags2D Prefix Sums, Max Subarray Sum
ACHard
Show Tags2D Prefix Sums, Trees
PlatinumVery Hard
Show Tags2D Prefix Sums

Difference Arrays

Focus Problem – try your best to solve this problem before continuing!

Explanation - Greg and Array

Let's create an array ss, where s[i]s[i] is the number of times operation ii is applied. The important step is how we update it.

For an interval [l,r][l, r], we can't loop through the interval and increment each value, as that would be O(MK)\mathcal{O}(MK) and too slow. Instead, we increment s[l]s[l] by one and decrement s[r+1]s[r+1] by one.

Now, we get the actual array by computing its prefix sum array, resulting in O(M)\mathcal{O}(M) time complexity. The second part, applying the operations, can be done exactly the same way.

Implementation - Greg and Array

Time Complexity: O(N+M)\mathcal{O}(N+M)

C++

#include <array>
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<int> a(n + 1);

Python

n, m, k = map(int, input().split())
arr = list(map(int, input().split()))
updates = []
for _ in range(m):
updates.append(list(map(int, input().split())))
s = [0] * (m + 2)
add = [0] * (n + 2)

Problems

StatusSourceProblem NameDifficultyTags
ACNormal
Show TagsDP, Difference Array
CFNormal
Show TagsDifference Array

Quiz

For a grid with NN rows and MM columns, what's the ideal time complexity to compute a 2D prefix sum array of the grid.

Question 1 of 4

Module Progress:

Join the USACO Forum!

Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!

PrevNext