Berlekamp–Massey Algorithm

The Berlekamp–Massey algorithm finds the shortest recurrence of a sequence. Given a sequence of length nn whose shortest recurrence has order mm, Berlekamp–Massey finds the shortest recurrence for every prefix of the sequence in O(nm)O(nm) time. In the worst case, m=O(n)m = O(n), so the worst-case complexity is O(n2)O(n^2).

Definition

For a sequence {a0…an−1}\{a_0 \dots a_{n - 1} \}, define a recurrence as a sequence {r0…rm}\{r_0\dots r_m\} satisfying

∑j=0mrjai−j=0,∀i≥m\sum_{j = 0} ^ m r_j a_{i - j} = 0, \forall i \ge m

where r0=1r_0 = 1. The value mm is called the order of the recurrence.

The shortest recurrence of the sequence {ai}\{a_i\} is the recurrence with the smallest order.

Method

For convenience, we use a slightly different set of recurrence coefficients, {f0…fm−1}\{f_0 \dots f_{m - 1}\}, satisfying

ai=∑j=0m−1fjai−j−1,∀i≥ma_i = \sum_{j = 0} ^ {m - 1} f_j a_{i - j - 1}, \forall i \ge m

It is easy to see that fi=−ri+1f_i = -r_{i + 1}, and that the order mm is the same under the two definitions.

We can construct the recurrence incrementally. Consider the terms of {ai}\{a_i\} in order, and adjust the coefficients {fi}\{f_i\} whenever the current recurrence gives an incorrect result. For convenience, let the shortest recurrence for the first ii terms be Fi={fi,j}F_i = \{f_{i, j}\}.

Initially, F0={}F_0 = \{\}. Suppose that Fi−1F_{i - 1} is valid for the first i−1i - 1 terms of {ai}\{a_i\}. For the ii-th term, there are two cases:

  1. The recurrence also gives the correct value for aia_i. No adjustment is needed; set Fi=Fi−1F_i = F_{i - 1}.
  2. The recurrence does not give the correct value for aia_i. Adjust Fi−1F_{i - 1} to obtain a new FiF_i.

Let Δi=ai−∑j=0mfi−1,jai−j−1\Delta_i = a_i - \sum_{j = 0} ^ m f_{i - 1, j} a_{i - j - 1} be the difference between aia_i and the value predicted by Fi−1F_{i - 1}.

If this is the first adjustment of the recurrence coefficients, then aia_i is the first nonzero term of the sequence. In this case, set FiF_i to ii zeroes, that is, to 00 coefficients. This is clearly a valid shortest recurrence.

Otherwise, let kk be the number of terms of {ai}\{a_i\} considered when the recurrence coefficients were last adjusted. Suppose there is a sequence G={g0…gm′−1}G = \{g_0 \dots g_{m' - 1}\} satisfying

∑j=0m′−1gjai′−j−1=0,∀i′∈[m′,i)\sum_{j = 0} ^ {m' - 1} g_j a_{i' - j - 1} = 0, \forall i' \in [m', i)

and ∑j=0m′−1gjai−j−1=Δi\sum_{j = 0} ^ {m' - 1} g_j a_{i - j - 1} = \Delta_i. Then adding FkF_k and GG term by term produces a valid recurrence coefficient sequence FiF_i.

To construct GG, one possible choice is

G={0,0,…,0,ΔiΔk,−ΔiΔkFk−1}G = \{0, 0, \dots, 0, \frac{\Delta_i}{\Delta_k}, -\frac{\Delta_i}{\Delta_k}F_{k-1}\}

There are i−k−1i - k - 1 leading 00 coefficients. The final term, −ΔiΔkFk−1-\frac{\Delta_i}{\Delta_k} F_{k-1}, means that every term of Fk−1F_{k-1} is multiplied by −ΔiΔk-\frac{\Delta_i}{\Delta_k} and appended to the sequence.

Indeed, ∑j=0m′−1gjai−j−1=ΔkΔiΔk=Δi\sum_{j = 0} ^ {m' - 1} g_j a_{i - j - 1} = \Delta_k \frac{\Delta_i}{\Delta_k} = \Delta_i, so this construction gives a valid GG. Set FiF_i to the termwise sum of FkF_k and GG.

If the recurrence is required in the original form {ri}\{r_i\}, negate every {fj}\{f_j\} and insert r0=1r_0 = 1 at the beginning.

The algorithm above has complexity O(nm)O(nm) when the shortest recurrence has order mm. Since m=O(n)m = O(n) in the worst case, the worst-case complexity is O(n2)O(n^2).

When implementing the algorithm, each adjustment only needs the recurrence coefficients FkF_k from the previous adjustment. Therefore, if only the shortest recurrence of the whole sequence is required, it is enough to store the current coefficients and the coefficients from the previous adjustment, using O(n)O(n) space.

Reference implementation
vector<int> berlekamp_massey(const vector<int> &a) {
  vector<int> v, last;  // v is the answer, 0-based, p is the module
  int k = -1, delta = 0;

  for (int i = 0; i < (int)a.size(); i++) {
    int tmp = 0;
    for (int j = 0; j < (int)v.size(); j++)
      tmp = (tmp + (long long)a[i - j - 1] * v[j]) % p;

    if (a[i] == tmp) continue;

    if (k < 0) {
      k = i;
      delta = (a[i] - tmp + p) % p;
      v = vector<int>(i + 1);

      continue;
    }

    vector<int> u = v;
    int val = (long long)(a[i] - tmp + p) * power(delta, p - 2) % p;

    if (v.size() < last.size() + i - k) v.resize(last.size() + i - k);

    (v[i - k - 1] += val) %= p;

    for (int j = 0; j < (int)last.size(); j++) {
      v[i - k + j] = (v[i - k + j] - (long long)val * last[j]) % p;
      if (v[i - k + j] < 0) v[i - k + j] += p;
    }

    if ((int)u.size() - i < (int)last.size() - k) {
      last = u;
      k = i;
      delta = a[i] - tmp;
      if (delta < 0) delta += p;
    }
  }

  for (auto &x : v) x = (p - x) % p;
  v.insert(v.begin(), 1);

  return v;  // $\forall i, \sum_{j = 0} ^ m a_{i - j} v_j = 0$
}

The naive Berlekamp–Massey algorithm finds the shortest recurrence of a finite sequence. If the sequence is infinite and an upper bound on the order of its shortest recurrence is known, taking the first 2m2m terms is enough to determine the shortest recurrence of the entire sequence. (Proof omitted.)

Applications

Because the Berlekamp–Massey algorithm has relatively poor numerical stability, it is rarely used for problems over the real numbers. For convenience, all computations below are assumed to take place in the residue field of a prime pp.

Finding the shortest recurrence of a vector or matrix sequence

To find the shortest recurrence of a vector sequence vi\boldsymbol{v}_i, let the vector dimension be nn. Choose a random nn-dimensional row vector uT\mathbf u^T, and find the shortest recurrence of the scalar sequence {uTvi}\{\boldsymbol{u}^T\boldsymbol{v}_i\}. By the Schwartz–Zippel lemma, the two shortest recurrences are identical with probability at least 1−np1 - \frac n p.

The matrix case is similar. For a matrix sequence {Ai}\{A_i\} of size n×mn \times m, choose a random 1×n1 \times n row vector uT\mathbf u^T and a random m×1m \times 1 column vector v\boldsymbol{v}, then find the shortest recurrence of the scalar sequence {uTAiv}\{\boldsymbol{u}^T A_i \boldsymbol{v}\}. Again by the Schwartz–Zippel lemma, the probability that the two recurrences agree is at least 1−n+mp1 - \frac{n + m} p.

Optimizing matrix exponentiation

Let fi\boldsymbol{f}_i be an nn-dimensional column vector satisfying fi=Afi−1\boldsymbol{f}_i = A \boldsymbol{f}_{i - 1}. Then {fi}\{\boldsymbol{f}_i\} is a vector sequence satisfying a linear recurrence of order at most nn. (Proof omitted.)

We can directly compute f0…f2n−1\boldsymbol{f}_0 \dots \boldsymbol{f}_{2n - 1}, find the shortest recurrence of {fi}\{\boldsymbol{f}_i\} using the method above, and then invoke Homogeneous linear recurrence with constant coefficients.

If fm\boldsymbol{f}_m is required, the complexity is O(n3+nlog⁡nlog⁡m)O(n^3 + n\log n \log m). If AA is sparse with only kk nonzero entries, this decreases to O(nk+nlog⁡nlog⁡m)O(nk + n\log n \log m). However, since the algorithm already needs O(nk)O(nk) time for preprocessing, an O(n2log⁡m)O(n^2 \log m) linear-recurrence algorithm is also acceptable when the constraints are not tight.

Finding the minimal polynomial of a matrix

The minimal polynomial of a square matrix AA is the polynomial ff of smallest degree satisfying f(A)=0f(A) = 0.

The minimal polynomial is exactly the shortest recurrence of {Ai}\{A^i\}, so it can be found directly with the Berlekamp–Massey algorithm. If AA is an nn-th order square matrix, the degree of its minimal polynomial is clearly at most nn.

The bottleneck is obtaining AiA^i, since multiplying matrices directly at every step would have complexity O(n4)O(n^4). But when finding the shortest recurrence of a matrix sequence, we are actually finding the shortest recurrence of {uTAiv}\{\boldsymbol{u}^T A^i \boldsymbol{v}\}. Thus it is enough to compute AivA^i \boldsymbol{v}.

If AA has kk nonzero entries, the complexity is O(kn+n2)O(kn + n^2).

Finding the determinant of a sparse matrix

If the characteristic polynomial of a square matrix AA is known, its constant term multiplied by (−1)n(-1)^n is the determinant. However, the minimal polynomial is not necessarily the characteristic polynomial.

If AA is multiplied by a random diagonal matrix BB, the minimal polynomial of ABAB is the characteristic polynomial with probability at least 1−2n2−np1 - \frac {2n^2 - n} p. It is then enough to divide by det  B\text{det}\;B.

For an nn-th order square matrix AA with kk nonzero entries, the complexity is O(kn+n2)O(kn + n ^ 2).

Finding the rank of a sparse matrix

For an n×mn\times m matrix AA, first choose a random n×nn\times n diagonal matrix PP and a random m×mm\times m diagonal matrix QQ, then find the minimal polynomial of QAPATQQ A P A^T Q.

There is no need to perform matrix multiplication explicitly. When finding the minimal polynomial, we only need to multiply QAPATQQ A P A^T Q by a vector, so we can apply these matrices to the vector one after another. The answer is the degree of the minimal polynomial after removing all factors of xx.

If AA has kk nonzero entries and n≤mn \le m, the complexity is O(kn+n2)O(kn + n ^ 2).

Solving a sparse system of equations

Problem: Given Ax=bA \mathbf x = \mathbf b, where AA is a full-rank sparse n×nn \times n matrix and b\mathbf b and x\mathbf x are 1×n1\times n column vectors, solve for xx in less than nωn^\omega time. A,bA, \mathbf b are known.

Method: Clearly, x=A−1b\mathbf x = A^{-1} \mathbf b. If we can find the shortest recurrence {r0…rm−1}\{r_0 \dots r_{m - 1}\} of {Aib}\{A^i \mathbf b\} for i≥0i \ge 0, where m≤nm \le n, then

A−1b=−1rm−1∑i=0m−2Aibrm−2−iA^{-1} \mathbf b = -\frac 1 {r_{m - 1}} \sum_{i = 0} ^ {m - 2} A^i \mathbf b r_{m - 2 - i}

(Proof omitted.)

Since AA is sparse, compute b…A2n−1b\mathbf b \dots A^{2n - 1} \mathbf b directly from the definition of the recurrence.

If AA has kk nonzero entries, the complexity is likewise O(kn+n2)O(kn + n^2).

Reference implementation
vector<int> solve_sparse_equations(const vector<tuple<int, int, int>> &A,
                                   const vector<int> &b) {
  int n = (int)b.size();  // 0-based

  vector<vector<int>> f({b});

  for (int i = 1; i < 2 * n; i++) {
    vector<int> v(n);
    auto &u = f.back();

    for (auto [x, y, z] : A)  // [x, y, value]
      v[x] = (v[x] + (long long)u[y] * z) % p;

    f.push_back(v);
  }

  vector<int> w(n);
  mt19937 gen;
  for (auto &x : w) x = uniform_int_distribution<int>(1, p - 1)(gen);

  vector<int> a(2 * n);
  for (int i = 0; i < 2 * n; i++)
    for (int j = 0; j < n; j++) a[i] = (a[i] + (long long)f[i][j] * w[j]) % p;

  auto c = berlekamp_massey(a);
  int m = (int)c.size();

  vector<int> ans(n);

  for (int i = 0; i < m - 1; i++)
    for (int j = 0; j < n; j++)
      ans[j] = (ans[j] + (long long)c[m - 2 - i] * f[i][j]) % p;

  int inv = power(p - c[m - 1], p - 2);

  for (int i = 0; i < n; i++) ans[i] = (long long)ans[i] * inv % p;

  return ans;
}

Examples

  1. LibreOJ #163. Gaussian elimination 2
  2. ICPC2021 Taipei Gym103443E. Composition with Large Red Plane, Yellow, Black, Gray, and Blue

This content comes from OI Wiki: Berlekamp–Massey Algorithm. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.