Given and , compute for ; find the recurrence from terms (Berlekamp–Massey, and Reeds–Sloane for a composite modulus); and recurrences with polynomial coefficients (P-recursive sequences). Uses polyMul from NTT.

The unifying view. With the characteristic polynomial : if then . All fast methods compute ; they differ in how they multiply.

MethodMultiplicationTimeWhen
iteratesmall
matrix power (Matrices)matrices, or a general linear DP
Kitamasaschoolbook mod , any modulus
Bostan–MoriNTT on large
closed form roots of nice roots only; numerically dangerous

For , : matrices operations, Kitamasa , Bostan–Mori about . A root of multiplicity contributes to the closed form.

The combination is the technique: brute-force about terms, run Berlekamp–Massey, verify the recurrence predicts the terms it was not fitted to, then Bostan–Mori to . It works because many sequences satisfy linear recurrences: walks in a fixed graph, tilings of a board, any DP with a fixed finite state set and constant transitions, determinants of banded matrices, many counting sequences. A transition matrix gives one through its characteristic polynomial (Cayley–Hamilton, Characteristic Polynomial; Wiedemann for sparse ones).

ExtensionHow
constant / polynomial of degree / / extend the state with / (binomial updates) / / all products; or just let Berlekamp–Massey find the longer recurrence
prefix sums order : multiply by
every -th term also linear recurrent (of order ): Graeffe-type step or BM on the subsequence
product , sum linear recurrent of order /
vector<mint> berlekampMassey(const vector<mint>& s) {      // shortest c with s[i] = sum_j c[j] s[i-1-j] for all i >= |c|
    int n = sz(s), L = 0, m = 0; vector<mint> C(n + 1, 0), B(n + 1, 0), T; C[0] = B[0] = 1; mint b = 1;
    for (int i = 0; i < n; i++) {
        m++; mint d = s[i]; for (int j = 1; j <= L; j++) d += C[j] * s[i - j];
        if (d == mint(0)) continue;
        T = C; mint coef = d / b;
        for (int j = m; j <= n; j++) C[j] -= coef * B[j - m];
        if (2 * L > i) continue;
        L = i + 1 - L; B = T; b = d; m = 0;
    }
    vector<mint> c(L); for (int j = 0; j < L; j++) c[j] = -C[j + 1];
    return c;
}
mint kitamasa(const vector<mint>& f, const vector<mint>& c, ll n) {   // f_i = sum_j c[j] f_{i-1-j}; O(k^2 log n)
    int k = sz(c); if (n < sz(f)) return f[n];
    if (k == 0) return 0;
    auto mulMod = [&](const vector<mint>& a, const vector<mint>& b) {
        vector<mint> r(2 * k, 0);
        for (int i = 0; i < k; i++) if (a[i] != mint(0)) for (int j = 0; j < k; j++) r[i + j] += a[i] * b[j];
        for (int i = 2 * k - 1; i >= k; i--) if (r[i] != mint(0)) for (int j = 1; j <= k; j++) r[i - j] += r[i] * c[j - 1];   // x^k = sum c_j x^{k-j}
        r.resize(k); return r;
    };
    vector<mint> res(k, 0), base(k, 0); res[0] = 1;
    if (k == 1) base[0] = c[0]; else base[1] = 1;
    for (; n; n >>= 1, base = mulMod(base, base)) if (n & 1) res = mulMod(res, base);
    mint ans = 0; for (int i = 0; i < k; i++) ans += res[i] * f[i];
    return ans;
}
mint bostanMori(Poly P, Poly Q, ll n) {                     // [x^n] P / Q, Q[0] != 0, O(k log k log n)
    while (n) {
        Poly Qn = Q; for (int i = 1; i < sz(Qn); i += 2) Qn[i] = -Qn[i];
        Poly U = polyMul(P, Qn), V = polyMul(Q, Qn);
        P.assign((sz(U) + 1) / 2 + 1, 0); Q.assign((sz(V) + 1) / 2, 0);
        for (int i = n & 1; i < sz(U); i += 2) P[i / 2] = U[i];
        for (int i = 0; i < sz(V); i += 2) Q[i / 2] = V[i];
        n >>= 1;
    }
    return P.empty() ? mint(0) : P[0] / Q[0];
}
mint linearRecurrenceNth(const vector<mint>& f, const vector<mint>& c, ll n) {   // via Bostan-Mori
    int k = sz(c); if (n < sz(f)) return f[n];
    Poly Q(k + 1); Q[0] = 1; for (int j = 0; j < k; j++) Q[j + 1] = -c[j];
    Poly P = polyMul(Poly(f.begin(), f.begin() + k), Q); P.resize(k);   // P = F Q mod x^k
    return bostanMori(P, Q, n);
}
mint guessNth(const vector<mint>& terms, ll n) { return linearRecurrenceNth(terms, berlekampMassey(terms), n); }

Reeds–Sloane: composite modulus

Berlekamp–Massey divides by discrepancies, which fails modulo a composite . Reeds–Sloane (1985) factors ; for each prime power it runs coupled Berlekamp–Massey instances (one per power of dividing the discrepancy, using only units for division), and the connection polynomials are glued with CRT. Give at least terms for a recurrence of order ; cost . For a prime modulus use plain Berlekamp–Massey.

struct ReedsSloane {
    using vec = vector<ll>;
    static void extend(vec& a, size_t d, ll value = 0) { if (d > a.size()) a.resize(d, value); }
    static ll exgcd(ll a, ll b, ll& x, ll& y) { if (!b) { x = 1, y = 0; return a; } ll g = exgcd(b, a % b, y, x); y -= x * (a / b); return g; }
    static ll inverse(ll a, ll m) { ll x, y; return exgcd(a, m, x, y) == 1 ? (x % m + m) % m : -1; }
    static int L(const vec& a, const vec& b) {
        int da = (sz(a) > 1 || (sz(a) == 1 && a[0])) ? sz(a) - 1 : -40000, db = (sz(b) > 1 || (sz(b) == 1 && b[0])) ? sz(b) - 1 : -40000;
        return max(da, db + 1);
    }
    static pair<vec, vec> primePower(const vec& s, ll mod, ll p, int e) {   // mod = p^e
        vector<vec> a(e), b(e), an(e), bn(e), ao(e), bo(e);
        vec t(e), u(e), r(e), to(e, 1), uo(e), pw(e + 1, 1);
        for (int i = 1; i <= e; i++) pw[i] = pw[i - 1] * p;
        for (int i = 0; i < e; i++) {
            a[i] = {pw[i]}, an[i] = {pw[i]}, b[i] = {0}, bn[i] = {s[0] * pw[i] % mod}, t[i] = s[0] * pw[i] % mod;
            if (t[i] == 0) t[i] = 1, u[i] = e; else for (u[i] = 0; t[i] % p == 0; t[i] /= p, u[i]++) {}
        }
        for (size_t k = 1; k < s.size(); k++) {
            for (int g = 0; g < e; g++) if (L(an[g], bn[g]) > L(a[g], b[g])) { ao[g] = a[e - 1 - u[g]], bo[g] = b[e - 1 - u[g]], to[g] = t[e - 1 - u[g]], uo[g] = u[e - 1 - u[g]], r[g] = k - 1; }
            a = an, b = bn;
            for (int o = 0; o < e; o++) {
                ll d = 0; for (size_t i = 0; i < a[o].size() && i <= k; i++) d = (d + (i128)a[o][i] * s[k - i]) % mod;
                if (d == 0) { t[o] = 1, u[o] = e; continue; }
                for (u[o] = 0, t[o] = d; t[o] % p == 0; t[o] /= p, u[o]++) {}
                int g = e - 1 - u[o];
                if (L(a[g], b[g]) == 0) { extend(bn[o], k + 1); bn[o][k] = (bn[o][k] + d) % mod; }
                else {
                    ll coef = (i128)t[o] * inverse(to[g], mod) % mod * pw[u[o] - uo[g]] % mod; int m = k - r[g];
                    extend(an[o], ao[g].size() + m); extend(bn[o], bo[g].size() + m);
                    for (size_t i = 0; i < ao[g].size(); i++) an[o][i + m] = ((an[o][i + m] - (i128)coef * ao[g][i]) % mod + mod) % mod;
                    while (!an[o].empty() && an[o].back() == 0) an[o].pop_back();
                    for (size_t i = 0; i < bo[g].size(); i++) bn[o][i + m] = ((bn[o][i + m] - (i128)coef * bo[g][i]) % mod + mod) % mod;
                    while (!bn[o].empty() && bn[o].back() == 0) bn[o].pop_back();
                }
            }
        }
        return {an[0], bn[0]};
    }
    static pair<vec, int> connection(vec s, ll mod) {      // A (A[0] = 1) and L: sum_j A[j] s[i-j] = 0 for i >= L
        vector<tuple<ll, ll, int>> fac; ll m = mod;
        for (ll p = 2; p * p <= m; p++) if (m % p == 0) { ll pe = 1; int e = 0; while (m % p == 0) m /= p, pe *= p, e++; fac.emplace_back(pe, p, e); }
        if (m > 1) fac.emplace_back(m, m, 1);
        vector<vec> as; size_t n = 0; int len = 0;
        for (auto [pe, p, e] : fac) {
            vec ss = s; for (auto& x : ss) x = (x % pe + pe) % pe;
            auto [a, b] = primePower(ss, pe, p, e);
            len = max(len, L(a, b)); as.push_back(a); n = max(n, as.back().size());
        }
        vec res(n, 0);
        for (size_t i = 0; i < n; i++) {                    // CRT of the coefficients
            ll x = 0, M = 1;
            for (size_t j = 0; j < fac.size(); j++) { ll pe = get<0>(fac[j]), c = i < as[j].size() ? as[j][i] : 0; ll k = (c - x % pe + pe) % pe * inverse(M % pe, pe) % pe; x += M * k, M *= pe; }
            res[i] = x % mod;
        }
        len = max(len, 0); res.resize(max<size_t>(res.size(), len + 1), 0);
        return {res, len};
    }
};
vector<ll> reedsSloane(const vector<ll>& s, ll mod) {      // s[i] = sum_{j=1}^{d} c[j-1] s[i-j] (mod m) for i >= d = |c|
    if (mod == 1 || s.empty()) return {};
    auto [A, len] = ReedsSloane::connection(s, mod);
    vector<ll> c(len); for (int j = 1; j <= len; j++) c[j - 1] = (mod - A[j] % mod) % mod;
    return c;
}
ll linearRecurrenceMod(const vector<ll>& first, const vector<ll>& c, ll n, ll mod) {   // Kitamasa for any modulus
    int d = sz(c); if (n < sz(first)) return first[n] % mod;
    if (d == 0) return 0;
    auto mulMod = [&](const vector<ll>& a, const vector<ll>& b) {
        vector<ll> r(2 * d, 0);
        for (int i = 0; i < d; i++) if (a[i]) for (int j = 0; j < d; j++) r[i + j] = (r[i + j] + (i128)a[i] * b[j]) % mod;
        for (int i = 2 * d - 1; i >= d; i--) if (r[i]) for (int j = 1; j <= d; j++) r[i - j] = (r[i - j] + (i128)r[i] * c[j - 1]) % mod;
        r.resize(d); return r;
    };
    vector<ll> res(d, 0), base(d, 0); res[0] = 1 % mod;
    if (d == 1) base[0] = c[0] % mod; else base[1] = 1;
    for (ll e = n; e > 0; e >>= 1, base = mulMod(base, base)) if (e & 1) res = mulMod(res, base);
    ll ans = 0; for (int i = 0; i < d; i++) ans = (ans + (i128)res[i] * first[i]) % mod;
    return ans;
}

P-recursive sequences

with polynomial coefficients of degree (“holonomic”): factorials (), Catalan (), Motzkin, Schröder, central binomials, derangements, any algebraic generating function, sums of hypergeometric terms. Guess the relation from about terms by solving for the unknown coefficients (each gives one linear equation), then extend in ; the -th term for huge uses polynomial matrix products with sample shifting (, as for in Evaluation and Interpolation).

vector<vector<mint>> findPRecursive(const vector<mint>& terms, int deg) {   // rel[i][e]: coefficient of n^e in P_i, at shift n - i
    int n = sz(terms), B = (n + 2) / (deg + 2), C = B * (deg + 1), R = n - (B - 1);
    if (B < 2 || R < C - 1) return {};
    vector<vector<mint>> mat(R, vector<mint>(C));
    for (int y = 0; y < R; y++) for (int b = 0; b < B; b++) { mint v = terms[y + b]; for (int e = 0; e <= deg; e++) mat[y][b * (deg + 1) + e] = v, v *= mint(y + b); }
    int rank = 0;
    for (int x = 0; x < C; x++) {
        int pivot = -1; for (int y = rank; y < R; y++) if (mat[y][x] != mint(0)) { pivot = y; break; }
        if (pivot < 0) break;
        swap(mat[rank], mat[pivot]); mint inv = mat[rank][x].inv();
        for (int x2 = x; x2 < C; x2++) mat[rank][x2] *= inv;
        for (int y = rank + 1; y < R; y++) if (mat[y][x] != mint(0)) { mint c = mat[y][x]; for (int x2 = x; x2 < C; x2++) mat[y][x2] -= c * mat[rank][x2]; }
        rank++;
    }
    if (rank == C) return {};
    for (int y = rank - 1; y >= 0; y--) if (mat[y][rank] != mint(0)) { mint c = mat[y][rank]; for (int y2 = 0; y2 < y; y2++) mat[y2][rank] -= c * mat[y2][y]; }
    int order = rank / (deg + 1); vector<vector<mint>> rel(order + 1, vector<mint>(deg + 1, 0));
    rel[0][rank % (deg + 1)] = 1;
    for (int y = rank - 1; y >= 0; y--) { int k = order - y / (deg + 1), e = y % (deg + 1); rel[k][e] = -mat[y][rank]; }
    return rel;
}
vector<mint> extendPRecursive(const vector<vector<mint>>& rel, vector<mint> terms, int n) {   // terms up to index n
    int order = sz(rel) - 1, deg = sz(rel[0]) - 1;
    while (sz(terms) <= n) {
        int m = sz(terms); mint s = 0;
        for (int i = 1; i <= order; i++) { int k = m - i; mint t = terms[k]; for (int e = 0; e <= deg; e++) s += t * rel[i][e], t *= mint(k); }
        mint den = 0, pw = 1; for (int e = 0; e <= deg; e++) den += pw * rel[0][e], pw *= mint(m);
        terms.push_back(-s / den);
    }
    terms.resize(n + 1); return terms;
}