Prefix Sums - Applicability & Use-Case


Prefix Sums Study Resources
USACO - Static Queries - [Prefix Sums + Sparse Tables]
Note that the Prefix Sum technique requires that the elements from the set have the existence of inverse property. For instance, in case of computing Range Sum, from [l, r]
, we effectively, add the additive inverse of pref[l-1]
to pref[r]
, to get the identity element 0
for the sum which is outside the range of [l, r]
, namely, elements from [0, l-1]
. We cannot therefore employ this technique to compute Range Min/Max as there is no inverse element for these operations.
The above technique does not work straight-off for non-commutative operations. How can it be adapted to this case?
This is a very important and provoking question. At its core, this questions the setup under which the Prefix Sum technique is applicable, with a flavor of generalizing this setup.
It turns out that if you are applying the Prefix Sum technique on the “field” (S,*)
, where S
is the set of elements (more importantly the class of elements or rather the type of elements (integers, strings, matrices, etc.)), and *
is the operation on which they are being accumulated. For instance, the basic setup of a Prefix “Sum“ array happens over (int,+)
. The general setup however requires the following properties on this field:
Any element of
S
sayx
must satisfy the following properties:
Existence of identity
There must exist an element
I
inS
such thatI*x=x*I=x
. Also, it is clear that the identity element is invariant to the general commutativity situation of the operation*
.Existence of inverse
For every
x
inS
, there must existinv(x)
inS
, such thatinv(x)*x = x*inv(x)=I
, whereI
is the identity element over*
inS
andinv(x)
denotes the inverse ofx
. Note, that again this expression is invariant to the general commutativity situation of the operator*
.
*
is associative*. “*Commutativity makes things easier. But even if it is non-commutative, we just have to extra careful in defining the inverse operation, which answering range queries”.
Think about matrix multiplication for the example of non-commutative associative. If *
is non-associative, there is no question that the prefix sum approach would not be applicable, at least not unambiguously. Consider the operator -
on A={1,2,3}
. The prefix “difference” array can be either {1,1-2,(1-2)-3}
or {1,1-2,1-(2-3)}
. So, there is ambiguity in defining the operation, and it can become complicated in execution, probably. Based on the traditional left associativity of -
, we may be tempted to move forward with one of these options, but that is completely dependent on intended use-case.
This paper discusses the applicability of the Prefix Sum technique, when the operation being tackled is associative with invariance to its commutativity property. Consider Matrix Multiplication and String Concatenation. (Both are associative and non-commutative)
Matrix Multiplication - Traditional computation of prefix products from left to right. To compute range product simply compute
inv(pref[l-1]) * pref[r]
, whereinv(A)
denotes the traditional inverse of a matrix.String Concatenation - Computation of the prefix array would entail concatenations of the cumulative strings, with the definition, that
s1+s2
iss2
appended tos1
. With this definition, it is easy to define the inverse of a string concatenation operation while computing a range concatenation. For instance if the string array is{“abc“, “def“, “ghi“}
, then the prefix array would be (still rather long){“abc“, “abcdef“, “abcdefghi“}
. To compute the range string concatenation withl=1, r=2
, we can perform the following computation.inv(pref[l-1]) + pref[r]
, whereinv(s)
denotes the string s reversed, and we perform character cancellation until we have exhaustedpref[l-1].size()
number of steps. Visualize a stack of characters.
struct matrix {}; // assume standard definition of a 2D matrix
struct query {int l,r;};
matrix a[] = {A,B,C,D};
matrix pref[];
for (i = 0 .. (a.size()-1)) pref[i] = (i ? pref[i-1] : I) * a[i]; // computes the matrix product
query q = query(l,r); // defines a query to compute matrix products from index l to r
matrix range_product = (l ? inv(pref[l-1]) : I) * pref[r]; // (pref[l-1])^(-1) * pref[r]
Subscribe to my newsletter
Read articles from Paras Khosla directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
