internal/internal_fps_avx2.hpp
lib/internal/internal_fps_avx2.hpp
インクルード
#include "internal/internal_fps_avx2.hpp"
依存するヘッダ(4)
-
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
このヘッダを使うライブラリ(4)
-
ベル数を $B_0$ から $B_n$ まで列挙する
lib/combinatorics/bell.hpp -
分割数を $p(0)$ から $p(n)$ まで列挙する
lib/combinatorics/partition_function.hpp -
第1種スターリング数(符号付き)s(n, 0), s(n, 1), ..., s(n, n) を求める(行)
lib/combinatorics/stirling.hpp -
形式的冪級数 (FPS)
lib/convolution/formal_power_series.hpp
検証プログラム(21)
-
test/yosupo/enumerative_conbinatorics/bell_number.test.cpp
test/yosupo/enumerative_conbinatorics/bell_number.test.cpp -
test/yosupo/enumerative_conbinatorics/partition_function.test.cpp
test/yosupo/enumerative_conbinatorics/partition_function.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind_fixed_k.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind_fixed_k.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind_fixed_k.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind_fixed_k.test.cpp -
test/yosupo/polynomial/composition_of_formal_power_series.test.cpp
test/yosupo/polynomial/composition_of_formal_power_series.test.cpp -
test/yosupo/polynomial/composition_of_formal_power_series_large.test.cpp
test/yosupo/polynomial/composition_of_formal_power_series_large.test.cpp -
test/yosupo/polynomial/compositional_inverse_of_formal_power_series.test.cpp
test/yosupo/polynomial/compositional_inverse_of_formal_power_series.test.cpp -
test/yosupo/polynomial/compositional_inverse_of_formal_power_series_large.test.cpp
test/yosupo/polynomial/compositional_inverse_of_formal_power_series_large.test.cpp -
test/yosupo/polynomial/division_of_polynomials.test.cpp
test/yosupo/polynomial/division_of_polynomials.test.cpp -
test/yosupo/polynomial/exp_of_formal_power_series.test.cpp
test/yosupo/polynomial/exp_of_formal_power_series.test.cpp -
test/yosupo/polynomial/inv_of_formal_power_series.test.cpp
test/yosupo/polynomial/inv_of_formal_power_series.test.cpp -
test/yosupo/polynomial/inv_of_polynomials.test.cpp
test/yosupo/polynomial/inv_of_polynomials.test.cpp -
test/yosupo/polynomial/log_of_formal_power_series.test.cpp
test/yosupo/polynomial/log_of_formal_power_series.test.cpp -
test/yosupo/polynomial/multipoint_evaluation.test.cpp
test/yosupo/polynomial/multipoint_evaluation.test.cpp -
test/yosupo/polynomial/polynomial_interpolation.test.cpp
test/yosupo/polynomial/polynomial_interpolation.test.cpp -
test/yosupo/polynomial/polynomial_taylor_shift.test.cpp
test/yosupo/polynomial/polynomial_taylor_shift.test.cpp -
test/yosupo/polynomial/pow_of_formal_power_series.test.cpp
test/yosupo/polynomial/pow_of_formal_power_series.test.cpp -
test/yosupo/polynomial/shift_of_sampling_points_of_polynomial.test.cpp
test/yosupo/polynomial/shift_of_sampling_points_of_polynomial.test.cpp -
test/yosupo/polynomial/sqrt_of_formal_power_series.test.cpp
test/yosupo/polynomial/sqrt_of_formal_power_series.test.cpp
実装
#pragma once
#include <algorithm>
#include <bit>
#include <cstdint>
#include <vector>
#include "internal/internal_fft_avx2.hpp"
// Montgomery + AVX2 NTT を土台にした FPS の inv / exp / log。
// 係数はすべて [0, 2*mod) の Montgomery 表現で持ち、変換は internal::avx2::butterfly /
// butterfly_inv をそのまま使う。Newton 法の各段で DFT を可能な限り使い回し、
// 通常の convolution ベース実装より NTT 回数を削減する。
// AVX2 を持たない CPU では未定義動作なので、呼び出し側 (fps) が実行時判定で振り分けること。
#define ALGO_AVX2_TARGET __attribute__((target("avx2")))
namespace internal {
namespace avx2 {
// 2 つの Montgomery バッファのレーンごとの積 (in-place, a *= b)。
// 8 レーン単位で処理し、端数 (n が 8 の倍数でない場合) はスカラで処理する。
template <u32 mod>
ALGO_AVX2_TARGET void hadamard(u32 *a, const u32 *b, int n) {
using s = mont<mod>;
static const montx8<mod> v;
int i = 0;
for (; i + 8 <= n; i += 8) {
__m256i x = _mm256_loadu_si256((const __m256i *)(a + i));
__m256i y = _mm256_loadu_si256((const __m256i *)(b + i));
_mm256_storeu_si256((__m256i *)(a + i), v.mul(x, y));
}
for (; i < n; ++i) a[i] = s::mul(a[i], b[i]);
}
// a を定数 c (Montgomery) でスケールする (in-place)。端数はスカラで処理する。
template <u32 mod>
ALGO_AVX2_TARGET void scale(u32 *a, u32 c, int n) {
using s = mont<mod>;
static const montx8<mod> v;
__m256i vc = _mm256_set1_epi32((int)c);
int i = 0;
for (; i + 8 <= n; i += 8) {
__m256i x = _mm256_loadu_si256((const __m256i *)(a + i));
_mm256_storeu_si256((__m256i *)(a + i), v.mul(x, vc));
}
for (; i < n; ++i) a[i] = s::mul(a[i], c);
}
// 長さ z (2 冪) の逆変換用スケール係数 1/z を Montgomery 表現で返す。
template <u32 mod>
ALGO_AVX2_TARGET u32 inv_len(int z) {
using s = mont<mod>;
return s::inv(s::to((u32)z));
}
// 逆変換 + 1/z スケールをまとめて行う (in-place)。長さ z は 8 の倍数を想定。
template <u32 mod>
ALGO_AVX2_TARGET void idft_scaled(u32 *a, int z) {
butterfly_inv<mod>(a, z);
scale<mod>(a, inv_len<mod>(z), z);
}
// 1/i (i = 0..n, Montgomery 表現) を O(n) で構築する。inv_tbl[0] は未定義 (0 を入れる)。
// inv[i] = -(mod/i) * inv[mod%i] (mod は素数, i < mod)。
template <u32 mod>
ALGO_AVX2_TARGET std::vector<u32> inv_table(int n) {
using s = mont<mod>;
std::vector<u32> r(std::max(2, n + 1));
r[0] = 0;
r[1] = s::to(1);
for (int i = 2; i <= n; ++i) {
// -(mod/i) * inv[mod%i]
u32 t = s::mul(r[mod % i], s::to((u32)(mod / i)));
r[i] = s::sub(0, t);
}
return r;
}
// 線形畳み込み a * b (Montgomery 表現入出力, 長さ na+nb-1)。butterfly を直接使う。
template <u32 mod>
ALGO_AVX2_TARGET std::vector<u32> conv(const std::vector<u32> &a, const std::vector<u32> &b) {
int na = (int)a.size(), nb = (int)b.size();
if (!na || !nb) return {};
int z = std::bit_ceil<unsigned>((unsigned)std::max(1, na + nb - 1));
std::vector<u32> fa(z, 0), fb(z, 0);
for (int i = 0; i < na; ++i) fa[i] = a[i];
for (int i = 0; i < nb; ++i) fb[i] = b[i];
butterfly<mod>(fa.data(), z);
butterfly<mod>(fb.data(), z);
hadamard<mod>(fa.data(), fb.data(), z);
idft_scaled<mod>(fa.data(), z);
fa.resize(na + nb - 1);
return fa;
}
// inv: h の逆元を deg 項求める。Newton 法 (g の DFT を 2 度使い回す)。
// 入力 hm は Montgomery 表現の係数列 (長さ >= 1, hm[0] != 0)。戻り値も Montgomery, 長さ deg。
template <u32 mod>
ALGO_AVX2_TARGET std::vector<u32> inv(const std::vector<u32> &hm, int deg) {
using s = mont<mod>;
int hn = (int)hm.size();
std::vector<u32> res(deg, 0);
res[0] = s::inv(hm[0]);
for (int d = 1; d < deg; d <<= 1) {
int z = 2 * d;
std::vector<u32> f(z, 0), g(z, 0);
for (int i = 0; i < std::min(hn, z); ++i) f[i] = hm[i];
for (int i = 0; i < d; ++i) g[i] = res[i];
butterfly<mod>(g.data(), z); // g の DFT は 2 度使う
butterfly<mod>(f.data(), z);
hadamard<mod>(f.data(), g.data(), z);
idft_scaled<mod>(f.data(), z);
for (int i = 0; i < d; ++i) f[i] = 0; // 上位半分のみ残す
butterfly<mod>(f.data(), z);
hadamard<mod>(f.data(), g.data(), z);
idft_scaled<mod>(f.data(), z);
for (int i = d; i < std::min(z, deg); ++i) res[i] = s::sub(0, f[i]);
}
return res;
}
// log: h の log を deg 項求める。h[0] == 1 が前提。log(h) = ∫ h'/h。
// 入力 hm は Montgomery 表現 (長さ >= 1)。戻り値も Montgomery, 長さ deg。
template <u32 mod>
ALGO_AVX2_TARGET std::vector<u32> log(const std::vector<u32> &hm, int deg) {
using s = mont<mod>;
int hn = (int)hm.size();
std::vector<u32> res(deg, 0);
if (deg <= 1) return res;
// 微分 h' (長さ hn-1) と h^{-1} (deg 項) を畳み込み、deg-1 項取って積分する。
int dn = std::min(hn, deg) - 1;
if (dn <= 0) return res;
std::vector<u32> dh(dn);
for (int i = 0; i < dn; ++i) dh[i] = s::mul(hm[i + 1], s::to((u32)(i + 1)));
auto ih = inv<mod>(hm, deg);
int in = (int)ih.size();
int z = std::bit_ceil<unsigned>((unsigned)std::max(1, dn + in - 1));
std::vector<u32> a(z, 0), b(z, 0);
for (int i = 0; i < dn; ++i) a[i] = dh[i];
for (int i = 0; i < in; ++i) b[i] = ih[i];
butterfly<mod>(a.data(), z);
butterfly<mod>(b.data(), z);
hadamard<mod>(a.data(), b.data(), z);
idft_scaled<mod>(a.data(), z);
// 積分: res[i] = a[i-1] / i (i = 1 .. deg-1)
auto it = inv_table<mod>(deg - 1);
for (int i = 1; i < deg; ++i) {
if (i - 1 < z) res[i] = s::mul(a[i - 1], it[i]);
}
return res;
}
// exp: h の exp を deg 項求める。h[0] == 0 が前提。
// Newton 法で f = exp(h) と g = f^{-1} を併走させ、DFT(f), DFT(g) を段内で使い回す。
// 入力 hm は Montgomery 表現 (長さ >= 1, hm[0] == 0)。戻り値も Montgomery, 長さ deg。
template <u32 mod>
ALGO_AVX2_TARGET std::vector<u32> exp(const std::vector<u32> &hm, int deg) {
using s = mont<mod>;
int hn = (int)hm.size();
u32 one = s::to(1);
std::vector<u32> f(deg, 0), g(deg, 0);
f[0] = one;
g[0] = one;
// idx[i] = i, iv[i] = 1/i (Montgomery)。全段で使い回す (添字は最大で z = 2d < 2*deg)。
int mx = std::max(2, 2 * deg);
std::vector<u32> idx(mx + 1);
for (int i = 0; i <= mx; ++i) idx[i] = s::to((u32)i);
auto iv = inv_table<mod>(mx);
auto hc = [&](int i) -> u32 { return i < hn ? hm[i] : 0u; };
auto dhc = [&](int i) -> u32 { return i < hn ? s::mul(hm[i], idx[i]) : 0u; };
for (int d = 1; d < deg; d <<= 1) {
int z = 2 * d;
// dt = h'[0..d) = (h[i]*i)
std::vector<u32> dt(d);
for (int i = 0; i < d; ++i) dt[i] = dhc(i);
std::vector<u32> sf(d), tf(d), tg(d);
for (int i = 0; i < d; ++i) sf[i] = f[i];
for (int i = 0; i < d; ++i) tf[i] = f[i];
for (int i = 0; i < d; ++i) tg[i] = g[i];
tf = conv<mod>(tf, dt);
tf.resize(z, 0);
for (int i = 0; i < d; ++i) tf[i] = s::sub(tf[i], s::mul(f[i], idx[i]));
tf = conv<mod>(tf, tg);
tf.resize(z, 0);
for (int i = 0; i < d; ++i) tf[i] = s::sub(tf[i], dt[i]);
for (int i = 1; i < z; ++i) tf[i] = s::mul(tf[i], iv[i]);
tf[0] = 0;
for (int i = 0; i < std::min(hn, z); ++i) tf[i] = s::add(tf[i], hm[i]);
tf = conv<mod>(sf, tf);
for (int i = d; i < std::min(deg, z); ++i) f[i] = tf[i];
// g を 2d 項へ更新 (Newton inverse, 変換長 z = 2d, gt の DFT を 2 度使い回す)。
std::vector<u32> ft(z, 0), gt2(z, 0);
for (int i = 0; i < std::min(z, deg); ++i) ft[i] = f[i];
for (int i = 0; i < d; ++i) gt2[i] = g[i];
butterfly<mod>(ft.data(), z);
butterfly<mod>(gt2.data(), z);
hadamard<mod>(ft.data(), gt2.data(), z);
idft_scaled<mod>(ft.data(), z);
for (int i = 0; i < d; ++i) ft[i] = 0;
butterfly<mod>(ft.data(), z);
hadamard<mod>(ft.data(), gt2.data(), z);
idft_scaled<mod>(ft.data(), z);
for (int i = d; i < std::min(z, deg); ++i) g[i] = s::sub(0, ft[i]);
(void)hc;
}
return f;
}
// sqrt: g^2 = h を deg 項求める。h[0] != 0 が前提で、定数項の平方根 g[0] を g0 (Montgomery) で渡す。
// Newton 法で g = sqrt(h) と h_inv = g^{-1} を併走させ、DFT(h_inv) を段内で 2 度使い回す。
// 入力 hm は Montgomery 表現 (長さ >= 1)。戻り値も Montgomery, 長さ deg。
template <u32 mod>
ALGO_AVX2_TARGET std::vector<u32> sqrt(const std::vector<u32> &hm, int deg, u32 g0) {
using s = mont<mod>;
int hn = (int)hm.size();
u32 inv2 = s::inv(s::to(2));
std::vector<u32> g(deg, 0), hi(deg, 0); // g = sqrt(h), hi = g^{-1}
g[0] = g0;
hi[0] = s::inv(g0);
auto hc = [&](int i) -> u32 { return i < hn ? hm[i] : 0u; };
for (int d = 1; d < deg; d <<= 1) {
int z = 2 * d;
// sq = g^2 mod x^z
std::vector<u32> sq(z, 0);
for (int i = 0; i < d; ++i) sq[i] = g[i];
butterfly<mod>(sq.data(), z);
hadamard<mod>(sq.data(), sq.data(), z);
idft_scaled<mod>(sq.data(), z);
// e = (h - g^2) は x^d で割り切れる。e * hi の [d, z) が g の新しい項 (× 1/2)。
std::vector<u32> e(z, 0);
for (int i = d; i < z; ++i) e[i] = s::sub(hc(i), sq[i]);
std::vector<u32> ht(z, 0); // DFT(hi) は逆元更新でも再利用する
for (int i = 0; i < d; ++i) ht[i] = hi[i];
butterfly<mod>(ht.data(), z);
butterfly<mod>(e.data(), z);
hadamard<mod>(e.data(), ht.data(), z);
idft_scaled<mod>(e.data(), z);
for (int i = d; i < std::min(z, deg); ++i) g[i] = s::mul(inv2, e[i]);
if (z >= deg) break; // これ以上 hi を伸ばす必要はない
// hi を 2d 項へ更新 (Newton inverse, DFT(hi) を再利用)。
std::vector<u32> gt(z, 0);
for (int i = 0; i < z; ++i) gt[i] = g[i];
butterfly<mod>(gt.data(), z);
hadamard<mod>(gt.data(), ht.data(), z);
idft_scaled<mod>(gt.data(), z);
for (int i = 0; i < d; ++i) gt[i] = 0;
butterfly<mod>(gt.data(), z);
hadamard<mod>(gt.data(), ht.data(), z);
idft_scaled<mod>(gt.data(), z);
for (int i = d; i < z; ++i) hi[i] = s::sub(0, gt[i]);
}
return g;
}
} // namespace avx2
} // namespace internal
#undef ALGO_AVX2_TARGET