競技プログラミング用 C++ ライブラリ
components/fps_sparse_core.hpp · GitHub で見る
共通ページ
fps_detail::SparseBase<Engine> は、疎 FPS の次数の最大公約数による圧縮・小さな整数冪の計算を行う内部部品です。通常版の疎演算 から使います。項の型・収集処理は fps_sparse_terms.hpp で軽量版と共有します。
Terms・Kind・collect・get の引数・返り値・前提条件は通常版と同じです。Engine は solve<kind>・geometric<exponential>・scale を提供します。get は次数を圧縮して演算を委譲し、結果を元の次数へ戻します。SIMD 演算には依存しません。
非零係数数を \(k\)、求める係数数を \(n\) として、演算を含む時間は \(O(nk)\)、追加空間は \(O(n+k)\) です。
#ifndef FPS_SPARSE_CORE_HPP
#define FPS_SPARSE_CORE_HPP
#include "fps_sparse_terms.hpp"
#include <algorithm>
#include <cassert>
#include <numeric>
#include <utility>
#include <vector>
namespace fps_detail {
template <class E> struct SparseBase : SparseTerms {
static Terms multiply(const Terms &a, const Terms &b, int n) {
Terms result;
for (auto [i, x] : a) for (auto [j, y] : b) if (i < n - j) result.emplace_back(i + j, x * y);
std::sort(result.begin(), result.end(), [](auto a, auto b) { return a.first < b.first; });
std::size_t used = 0;
for (auto [i, x] : result) {
if (used && result[used - 1].first == i) result[used - 1].second += x;
else result[used++] = {i, x};
}
result.resize(used);
result.erase(std::remove_if(result.begin(), result.end(), [](auto term) { return term.second == mint(0); }), result.end());
return result;
}
template <Kind kind>
static Series get(Terms terms, int n, mint initial, mint exponent, const mint *(*inverses)(int)) {
assert(n >= 0 && terms.size() <= 64);
if constexpr (kind != Kind::inverse) assert(unsigned(n) < mint::mod());
if (!n) return {};
if (terms.empty()) { Series out(n); out[0] = initial; return out; }
if constexpr (kind == Kind::power) {
unsigned e = exponent.val();
if (e <= 4 && terms.size() <= 12 && (n >= 4096 || terms.size() <= 3)) {
Terms power{{0, 1}}, f{{0, 1}};
f.insert(f.end(), terms.begin(), terms.end());
while (e) {
if (e & 1) power = multiply(power, f, n);
e >>= 1;
if (e) f = multiply(f, f, n);
}
Series out(n);
for (auto [i, x] : power) out[i] = x * initial;
return out;
}
}
int stride = 0;
for (auto term : terms) stride = std::gcd(stride, term.first);
const int count = (n - 1) / stride + 1;
for (auto &term : terms) term.first /= stride;
const mint *inv = nullptr;
if constexpr (kind != Kind::inverse) inv = inverses(count);
Series compressed;
if (terms.size() == 1 && kind != Kind::power) {
compressed.resize(count);
if constexpr (kind == Kind::exponential) {
E::template geometric<true>(compressed, initial, terms[0].second, inv);
} else {
E::geometric(compressed, kind == Kind::logarithm ? mint(-1) : initial, -terms[0].second);
if constexpr (kind == Kind::logarithm) compressed[0] = 0, E::scale(compressed, inv);
}
} else compressed = E::template solve<kind>(terms, count, initial, exponent, inv);
if (stride == 1) return compressed;
Series out(n);
for (int i = 0; i < count; ++i) out[i * stride] = compressed[i];
return out;
}
};
}
#endif
#line 1 "components/fps_sparse_core.hpp"
#line 1 "components/fps_sparse_terms.hpp"
#line 1 "math/mint.hpp"
#include <cassert>
#include <istream>
#include <ostream>
#include <type_traits>
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC push_options
#pragma GCC optimize("O3")
#endif
namespace mint_detail {
template <class I>
constexpr bool mint_integral = std::is_integral_v<I>
#if defined(__SIZEOF_INT128__)
|| std::is_same_v<I, __int128> || std::is_same_v<I, unsigned __int128>
#endif
;
struct [[gnu::warn_unused]] Mint {
using mint = Mint;
unsigned v = 0;
[[nodiscard]] static constexpr unsigned mod() { return 998244353; }
[[nodiscard, gnu::always_inline]] static unsigned reduce(unsigned long long x) {
return (x + (unsigned long long)(unsigned(x) * 998244351u) * mod()) >> 32;
}
[[gnu::always_inline]] Mint() = default;
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
Mint(I x) {
long long r = x % (long long)mod();
if (r < 0) r += mod();
v = reduce((unsigned long long)r * 932051910);
}
[[nodiscard, gnu::always_inline]] static Mint raw(unsigned x) {
Mint a;
a.v = reduce((unsigned long long)x * 932051910);
return a;
}
[[nodiscard, gnu::always_inline]] static Mint word(unsigned x) {
Mint a;
a.v = x;
return a;
}
[[nodiscard, gnu::always_inline]] unsigned val() const {
unsigned r = reduce(v);
return r < mod() ? r : r - mod();
}
friend std::istream &operator>>(std::istream &is, Mint &x) {
long long value;
if (is >> value) x = Mint(value);
return is;
}
friend std::ostream &operator<<(std::ostream &os, const Mint &x) {
return os << x.val();
}
[[gnu::always_inline]] Mint &operator+=(Mint b) {
v += b.v;
if (v >= 2 * mod()) v -= 2 * mod();
return *this;
}
[[gnu::always_inline]] Mint &operator-=(Mint b) {
v -= b.v;
if (v >> 31) v += 2 * mod();
return *this;
}
[[gnu::always_inline]] Mint &operator*=(Mint b) {
v = reduce((unsigned long long)v * b.v);
return *this;
}
Mint &operator/=(Mint b) { return *this *= b.inv(); }
Mint &operator++() { return *this += Mint(1); }
Mint &operator--() { return *this -= Mint(1); }
Mint operator++(int) {
Mint a = *this;
++*this;
return a;
}
Mint operator--(int) {
Mint a = *this;
--*this;
return a;
}
[[nodiscard, gnu::always_inline]] Mint operator+() const { return *this; }
[[nodiscard, gnu::always_inline]] Mint operator-() const { return word(v ? 2 * mod() - v : 0); }
[[nodiscard, gnu::always_inline]] Mint operator+(Mint b) const { return Mint(*this) += b; }
[[nodiscard, gnu::always_inline]] Mint operator-(Mint b) const { return Mint(*this) -= b; }
[[nodiscard, gnu::always_inline]] Mint operator*(Mint b) const { return Mint(*this) *= b; }
[[nodiscard]] Mint operator/(Mint b) const { return Mint(*this) /= b; }
[[nodiscard, gnu::always_inline]] bool operator==(Mint b) const { return (v < mod() ? v : v - mod()) == (b.v < mod() ? b.v : b.v - mod()); }
[[nodiscard, gnu::always_inline]] bool operator!=(Mint b) const { return !(*this == b); }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator+(I a, Mint b) { return Mint(a) + b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator-(I a, Mint b) { return Mint(a) - b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator*(I a, Mint b) { return Mint(a) * b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator/(I a, Mint b) { return Mint(a) / b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend bool operator==(I a, Mint b) { return Mint(a) == b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend bool operator!=(I a, Mint b) { return Mint(a) != b; }
[[nodiscard]] Mint pow(long long n) const {
assert(n >= 0);
if (!n) return Mint(1);
Mint r(1), a = *this;
for (; n > 1; n >>= 1, a *= a)
if (n & 1) r *= a;
return r * a;
}
[[nodiscard, gnu::noinline]] Mint inv() const {
assert(*this != Mint(0));
Mint a2 = *this * *this, a3 = a2 * *this;
Mint a7 = a3 * a3 * *this;
Mint a31 = a7;
for (int i = 0; i < 2; ++i) a31 *= a31;
a31 *= a3;
Mint r = a7;
for (int i = 0; i < 3; ++i) r *= r;
r *= a3;
r *= r;
for (int j = 0; j < 4; ++j) {
for (int i = 0; i < 5; ++i) r *= r;
r *= a31;
}
for (int i = 0; i < 3; ++i) r *= r;
return r * a7;
}
};
} // namespace mint_detail
using mint = mint_detail::Mint;
using fps_mint = mint;
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC pop_options
#endif
#line 5 "components/fps_sparse_terms.hpp"
#include <algorithm>
#line 7 "components/fps_sparse_terms.hpp"
#include <numeric>
#include <utility>
#include <vector>
namespace fps_detail {
struct SparseTerms {
using Series = std::vector<mint>;
using Terms = std::vector<std::pair<int, mint>>;
enum class Kind { inverse, logarithm, exponential, power };
static Terms collect(const mint *f, int size, int n, int limit = 12) {
Terms terms;
for (int i = 1; i < std::min(size, n) && int(terms.size()) <= limit; ++i)
if (f[i] != mint(0)) terms.emplace_back(i, f[i]);
return terms;
}
};
}
#line 10 "components/fps_sparse_core.hpp"
namespace fps_detail {
template <class E> struct SparseBase : SparseTerms {
static Terms multiply(const Terms &a, const Terms &b, int n) {
Terms result;
for (auto [i, x] : a) for (auto [j, y] : b) if (i < n - j) result.emplace_back(i + j, x * y);
std::sort(result.begin(), result.end(), [](auto a, auto b) { return a.first < b.first; });
std::size_t used = 0;
for (auto [i, x] : result) {
if (used && result[used - 1].first == i) result[used - 1].second += x;
else result[used++] = {i, x};
}
result.resize(used);
result.erase(std::remove_if(result.begin(), result.end(), [](auto term) { return term.second == mint(0); }), result.end());
return result;
}
template <Kind kind>
static Series get(Terms terms, int n, mint initial, mint exponent, const mint *(*inverses)(int)) {
assert(n >= 0 && terms.size() <= 64);
if constexpr (kind != Kind::inverse) assert(unsigned(n) < mint::mod());
if (!n) return {};
if (terms.empty()) { Series out(n); out[0] = initial; return out; }
if constexpr (kind == Kind::power) {
unsigned e = exponent.val();
if (e <= 4 && terms.size() <= 12 && (n >= 4096 || terms.size() <= 3)) {
Terms power{{0, 1}}, f{{0, 1}};
f.insert(f.end(), terms.begin(), terms.end());
while (e) {
if (e & 1) power = multiply(power, f, n);
e >>= 1;
if (e) f = multiply(f, f, n);
}
Series out(n);
for (auto [i, x] : power) out[i] = x * initial;
return out;
}
}
int stride = 0;
for (auto term : terms) stride = std::gcd(stride, term.first);
const int count = (n - 1) / stride + 1;
for (auto &term : terms) term.first /= stride;
const mint *inv = nullptr;
if constexpr (kind != Kind::inverse) inv = inverses(count);
Series compressed;
if (terms.size() == 1 && kind != Kind::power) {
compressed.resize(count);
if constexpr (kind == Kind::exponential) {
E::template geometric<true>(compressed, initial, terms[0].second, inv);
} else {
E::geometric(compressed, kind == Kind::logarithm ? mint(-1) : initial, -terms[0].second);
if constexpr (kind == Kind::logarithm) compressed[0] = 0, E::scale(compressed, inv);
}
} else compressed = E::template solve<kind>(terms, count, initial, exponent, inv);
if (stride == 1) return compressed;
Series out(n);
for (int i = 0; i < count; ++i) out[i * stride] = compressed[i];
return out;
}
};
}