きたまさ法(任意可換環版、$O(k^2 \log n)$)
lib/linalg/linear_recurrence.hpp
概要
線形漸化式 $a_i = \sum_{j=1}^{k} d_j a_{i-j}$($i \ge k$)の第 $n$ 項を、 特性多項式 $f(x) = x^k - \sum_j d_j x^{k-1-j}$ を法とした $x^n \bmod f$ の 二乗法で求める。任意の可換環 T に対応(NTT 非対応 mod・dynamic_modint・ 行列・有理数など畳み込みできない環でも可)。NTT-friendly な static_modint で $O(M(k) \log n)$ が欲しい場合は同ファイルの kth_term を使う。
インクルード
#include "linalg/linear_recurrence.hpp"
関数
| 名前 | 説明 |
|---|---|
kitamasa |
きたまさ法(任意可換環版、$O(k^2 \log n)$) |
bostan_mori |
Bostan-Mori 法 |
kth_term |
線形漸化式の第 $n$ 項(NTT 版、$O(M(d) \log n)$) |
検証
依存するヘッダ(7)
-
NTT・畳み込み (convolution)
lib/convolution/ntt.hpp -
Montgomery + AVX2 による NTT 畳み込み (NTT-friendly な 32bit 素数 mod 専用)
lib/convolution/ntt_avx2.hpp -
internal/internal_fft.hpp
lib/internal/internal_fft.hpp -
internal/internal_fft_avx2.hpp
lib/internal/internal_fft_avx2.hpp -
internal/internal_math.hpp
lib/internal/internal_math.hpp -
internal/internal_type_traits.hpp
lib/internal/internal_type_traits.hpp -
modint (static_modint / dynamic_modint)
lib/number_theory/modint.hpp
検証プログラム(2)
-
test/aoj/alds1/kitamasa.test.cpp
test/aoj/alds1/kitamasa.test.cpp -
test/yosupo/math/bostan_mori.test.cpp
test/yosupo/math/bostan_mori.test.cpp
実装
#pragma once
#include <cassert>
#include <cstdint>
#include <utility>
#include <vector>
#include "convolution/ntt.hpp"
#include "convolution/ntt_avx2.hpp"
#include "number_theory/modint.hpp"
// 線形漸化式 a_i = sum_{j=1}^{k} d[j-1] a_{i-j} の第 n 項を求める。
// - kitamasa: 任意可換環版 (O(k^2 log n))。NTT 非対応 mod・dynamic_modint・行列・
// 有理数など畳み込みできない環でも可。
// - kth_term: NTT-friendly な static_modint 版 (O(M(k) log n))。Bostan-Mori 法で
// 有理形式的冪級数の係数として求め、AVX2 Montgomery NTT に振り分ける。
namespace internal {
// 特性多項式を法とした x^n mod f を二乗法で求めるきたまさ法の本体 (汎用環版, O(k^2 log n))。
// 漸化式 a_i = sum_{j=1}^{k} d[j-1] a_{i-j} に対し f(x) = x^k - sum_j d[j-1] x^{k-1-j}。
// x^k ≡ sum_j d[k-1-j] x^j (mod f) を使い、長さ <= 2k-1 の積を毎回 f で割って長さ k に落とす。
template <class T>
T kitamasa_naive(const std::vector<T> &a, const std::vector<T> &d, std::int64_t n) {
const int k = (int)d.size();
// xk[j] = x^k の x^j 係数 = d[k-1-j]。
std::vector<T> xk(k);
for (int j = 0; j < k; ++j) xk[j] = d[k - 1 - j];
// 長さ 2k-1 の多項式を x^{>=k} を順に潰して長さ k の剰余へ落とす。
auto reduce = [&](std::vector<T> &p) {
for (int i = (int)p.size() - 1; i >= k; --i) {
T c = p[i];
if (c == T()) continue;
p[i] = T();
for (int j = 0; j < k; ++j) p[i - k + j] += c * xk[j];
}
p.resize(k);
};
auto mulmod = [&](const std::vector<T> &p, const std::vector<T> &q) {
std::vector<T> r(p.size() + q.size() - 1, T());
for (int i = 0; i < (int)p.size(); ++i)
for (int j = 0; j < (int)q.size(); ++j) r[i + j] += p[i] * q[j];
reduce(r);
return r;
};
// res = 1, base = x (k=1 なら x ≡ d[0])。
std::vector<T> res(k, T());
res[0] = T(1);
std::vector<T> base(k, T());
if (k == 1) base[0] = d[0];
else base[1] = T(1);
while (n > 0) {
if (n & 1) res = mulmod(res, base);
n >>= 1;
if (n) base = mulmod(base, base);
}
T ans{};
for (int i = 0; i < k; ++i) ans += res[i] * a[i];
return ans;
}
// Bostan-Mori の 1 ステップ (スカラ NTT, DFT 使い回し)。p, q は通常表現。
// q(-x) (= 奇数次のみ符号反転) の順変換を p*q(-x) と q*q(-x) で共有し、
// 素朴な 2 畳み込み (6 NTT) を 3 順変換 + 2 逆変換に削減する。
// odd=true で次の p に奇数次成分を、false で偶数次成分を採る。
template <internal::static_modint_c mint>
void bostan_mori_step(std::vector<mint> &p, std::vector<mint> &q, bool odd) {
int np = (int)p.size(), nq = (int)q.size();
std::vector<mint> q_minus = q;
for (int i = 1; i < nq; i += 2) q_minus[i] = -q_minus[i];
int lp = np + nq - 1, lq = 2 * nq - 1;
if (std::min(np, nq) <= CONVOLUTION_NAIVE_THRESHOLD) {
p = convolution(p, q_minus);
q = convolution(q, q_minus);
} else {
int z = std::bit_ceil<unsigned>((unsigned)lq);
std::vector<mint> P(z), Q(z), QM(z);
for (int i = 0; i < np; ++i) P[i] = p[i];
for (int i = 0; i < nq; ++i) Q[i] = q[i], QM[i] = q_minus[i];
internal::butterfly(P);
internal::butterfly(Q);
internal::butterfly(QM);
for (int i = 0; i < z; ++i) P[i] *= QM[i];
for (int i = 0; i < z; ++i) Q[i] *= QM[i];
internal::butterfly_inv(P);
internal::butterfly_inv(Q);
mint iz = mint(z).inv();
for (int i = 0; i < lp; ++i) P[i] *= iz;
for (int i = 0; i < lq; ++i) Q[i] *= iz;
P.resize(lp);
Q.resize(lq);
p = std::move(P);
q = std::move(Q);
}
std::vector<mint> rp((lp + (odd ? 0 : 1)) / 2), rq((lq + 1) / 2);
for (int i = odd ? 1 : 0, k = 0; i < lp; i += 2, ++k) rp[k] = p[i];
for (int i = 0, k = 0; i < lq; i += 2, ++k) rq[k] = q[i];
p = std::move(rp);
q = std::move(rq);
}
} // namespace internal
/// @brief きたまさ法(任意可換環版、$O(k^2 \log n)$)
/// @complexity 漸化式の次数を $k$ として $O(k^2 \log n)$
/// @details 線形漸化式 $a_i = \sum_{j=1}^{k} d_j a_{i-j}$($i \ge k$)の第 $n$ 項を、
/// 特性多項式 $f(x) = x^k - \sum_j d_j x^{k-1-j}$ を法とした $x^n \bmod f$ の
/// 二乗法で求める。任意の可換環 `T` に対応(NTT 非対応 mod・`dynamic_modint`・
/// 行列・有理数など畳み込みできない環でも可)。NTT-friendly な `static_modint` で
/// $O(M(k) \log n)$ が欲しい場合は同ファイルの `kth_term` を使う。
/// @tparam T 可換環(加減乗算と `T(0)`/`T(1)` を持つ)
/// @param a 初項 $a_0, \ldots, a_{k-1}$
/// @param d 漸化式の係数 $d_1, \ldots, d_k$($a_i = d_1 a_{i-1} + \cdots + d_k a_{i-k}$)
/// @param n 求める項の番号
/// @return T $a_n$
template <class T>
T kitamasa(const std::vector<T> &a, const std::vector<T> &d, std::int64_t n) {
assert(a.size() == d.size());
const int k = (int)d.size();
if (n < k) return a[n];
return internal::kitamasa_naive(a, d, n);
}
/// @brief Bostan-Mori 法
/// @complexity 多項式次数を $d$ として $O(d \log d \log n)$
/// @details 有理形式的冪級数 $p(x)/q(x)$ の $x^n$ の係数を $O(M(d) \log n)$ で求める
/// ($d = \deg q$、$M$ はサイズ $d$ の畳み込みコスト)。各ステップで $q(-x)$ の順変換を
/// 共有し、AVX2 が使える NTT-friendly な mint なら Montgomery + AVX2 NTT を使う。
/// @tparam mint NTT-friendly な static modint
/// @param p 分子の係数列
/// @param q 分母の係数列(`q[0] != 0`)
/// @param n 求める項の番号($n < 0$ なら $0$)
/// @return mint $p(x)/q(x)$ の $x^n$ の係数
template <internal::static_modint_c mint>
mint bostan_mori(std::vector<mint> p, std::vector<mint> q, std::int64_t n) {
if (n < 0) return 0;
constexpr unsigned int mod = (unsigned int)mint::mod();
if constexpr (mod < (1u << 30)) {
// AVX2 が使えるなら Montgomery + AVX2 NTT 版に振り分ける。
if (mint::is_prime_mod() && internal::has_avx2()) {
std::vector<std::uint32_t> up(p.size()), uq(q.size());
for (std::size_t i = 0; i < p.size(); ++i) up[i] = (std::uint32_t)p[i].val();
for (std::size_t i = 0; i < q.size(); ++i) uq[i] = (std::uint32_t)q[i].val();
return mint::raw((int)internal::avx2::bostan_mori<mod>(std::move(up), std::move(uq), n));
}
}
while (n > 0) {
if ((int)p.size() >= (int)q.size()) p.resize(q.size() - 1);
internal::bostan_mori_step(p, q, n & 1);
n >>= 1;
}
return p[0] / q[0];
}
/// @brief 線形漸化式の第 $n$ 項(NTT 版、$O(M(d) \log n)$)
/// @complexity 漸化式の次数を $d$ として $O(d \log d \log n)$
/// @details $a_i = \sum_{j=1}^{d} c_j a_{i-j}$($i \ge d$)の第 $n$ 項を Bostan-Mori 法で求める。
/// NTT-friendly な `static_modint` 専用。畳み込みできない環では `kitamasa` を使う。
/// @tparam mint NTT-friendly な static modint
/// @param a 初項 $a_0, \ldots, a_{d-1}$
/// @param c 漸化式の係数 $c_1, \ldots, c_d$
/// @param n 求める項の番号
/// @return mint $a_n$
template <internal::static_modint_c mint>
mint kth_term(std::vector<mint> a, std::vector<mint> c, std::int64_t n) {
assert(a.size() == c.size());
int d = a.size();
if (n < d) return a[n];
std::vector<mint> q(d + 1);
q[0] = 1;
for (int i = 0; i < d; ++i) q[i + 1] = -c[i];
std::vector<mint> p = convolution(a, q);
p.resize(d);
return bostan_mori(p, q, n);
}