The single most important operation in this branch. Recognising a convolution turns an double loop into .
Recognising one
You have a convolution whenever the answer at index sums over all pairs of indices combining to :
| Problem | Combination |
|---|---|
| Polynomial multiplication | |
| ”Number of ways to get sum from two sets” | |
| Count pairs at distance | (reverse one array) |
| String matching with wildcards | correlation |
| Counting subsequence patterns | |
| Multiplying big integers | with carries |
| Counting XOR pairs | |
| Counting AND/OR pairs | / |
| Counting gcd pairs | — a Dirichlet convolution |
Which transform
| Combining rule | Transform | Cost |
|---|---|---|
| FFT / NTT | ||
| (cyclic) | FFT of length exactly | |
| FWHT | ||
| (OR) | subset-sum zeta/Möbius | |
| (AND) | superset-sum zeta/Möbius | |
| , disjoint | subset convolution (ranked SOS) | |
| Dirichlet convolution | ||
| prefix sums | ||
| , max-plus, convex inputs | Minkowski sum | |
| , max-plus, general | no known subquadratic algorithm |
The transform-based ones all share the same three-step shape:
transform(a); transform(b);
for i: c[i] = a[i] * b[i];
inverse_transform(c);
Only the transform differs. That structural uniformity is worth internalising.
The FFT route
vector<long long> multiply(vector<long long> a, vector<long long> b) {
int need = a.size() + b.size() - 1, n = 1;
while (n < need) n <<= 1;
vector<cd> fa(a.begin(), a.end()), fb(b.begin(), b.end());
fa.resize(n); fb.resize(n);
fft(fa, false); fft(fb, false);
for (int i = 0; i < n; i++) fa[i] *= fb[i];
fft(fa, true);
vector<long long> res(need);
for (int i = 0; i < need; i++) res[i] = llround(fa[i].real());
return res;
}Pad to a power of two at least ; padding to less causes cyclic wraparound.
Precision and modulus
| Situation | Method |
|---|---|
| Small integer coefficients, exact needed | NTT mod 998244353 |
| Answer mod an arbitrary prime | 3 NTT primes + CRT, or splitting |
| Real coefficients | complex FFT |
| Large integers, no modulus | split each coefficient into 15-bit halves; 4 transforms |
| Two real sequences at once | pack them as real and imaginary parts — halves the work |
doubleFFT precisionError grows roughly as . With and coefficients up to , plain complex FFT loses the low bits. Use NTT, or split coefficients.
Convolution without a transform
Sometimes the structure makes it cheap:
- Sparse inputs — if has non-zeros, direct evaluation is , better than FFT when is small.
- Short inputs — below , the loop beats FFT’s constant factor.
- Convex max-plus — merge the difference sequences in .
- One input is a prefix/suffix pattern — prefix sums suffice.
Multi-dimensional convolution
Transform along each axis in turn. A 2D convolution of an grid is — used for image filtering and for counting 2D patterns.
The general lesson
Convolution is the bridge between counting problems and fast algorithms. When a problem says “count the ways to combine one thing from each of two sets”, write down the generating function; the answer is a coefficient of a product, and the product is a convolution.
See Generating Functions for the counting side and FFT and NTT for the computation.
See also: FFT and NTT · FWHT · SOS DP