cp-book

ecnerwala's competitive programming library

View the Project on GitHub ecnerwala/cp-book

🧪 fft/engines/algebras.test.cpp

View this file on GitHub · Last update: 2026-07-30 21:24:54-07:00

Depends on

Code

#include <bits/stdc++.h>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_get_random_seed.hpp>

#include "fft/engines/algebras.hpp"
#include "fft/engines/ntt.hpp"
#include "fft/engines/split.hpp"
#include "fft/engines/crt.hpp"
#include "fft/multiply.hpp"
#include "fft/online.hpp"
#include "fft/test_util.test.hpp"
#include "modnum.hpp"

namespace ecnerwala {
namespace fft {

using namespace std;

// engine concept sanity checks for the wrapping algebras
static_assert(engine<engines::matrix<engines::ntt<modnum<998244353>>, 2>>);
static_assert(engine<engines::trunc<engines::ntt<modnum<998244353>>, 3>>);
// tracked inner engines work when the accumulated scale fits the budget (N <= 2)
static_assert(engine<engines::matrix<engines::split<modnum<int(1e9)+7>>, 2>>);
static_assert(engine<engines::trunc<engines::crt<modnum<int(1e9)+7>>, 2>>);
// the stable variants keep tracked inner engines sound at any N
static_assert(engine<engines::matrix_stable<engines::split<modnum<int(1e9)+7>>, 3>>);
static_assert(engine<engines::trunc_stable<engines::crt<modnum<int(1e9)+7>>, 3>>);

template <typename E, bool online, int N>
void test_matrix_engine(mt19937& mt) {
	using M = typename E::value_type;
	using num = std::remove_reference_t<decltype(std::declval<M&>()[{0, 0}])>;
	auto rnd_mat = [&]() {
		M m;
		for (int r = 0; r < N; r++) for (int c = 0; c < N; c++) m[{r, c}] = rnd_val<num>(mt);
		return m;
	};
	for (int la : {1, 2, 3, 17, 33}) {
		for (int lb : {1, 2, 16, 17}) {
			vector<M> a(la), b(lb);
			for (M& m : a) m = rnd_mat();
			for (M& m : b) m = rnd_mat();
			INFO("la = " << la << ", lb = " << lb);
			REQUIRE(multiply<E>(a, b) == multiply_slow(a, b));
		}
	}
	// square of a matrix sequence must keep both cross orders
	int n = 33;
	vector<M> f(n);
	for (M& m : f) m = rnd_mat();
	auto slow = multiply_slow(f, f);
	slow.resize(2*n, M{});
	vector<M> got(2*n - 1);
	square<E>(span<const M>(f), span<M>(got));
	REQUIRE(got == vector<M>(slow.begin(), slow.begin() + 2*n - 1));
	if constexpr (online) {
		online_squarer<E> os(n);
		for (int i = 0; i < n; i++) {
			os.push(f[i]);
			REQUIRE(os.back() == slow[i]);
		}
	}
}

TEMPLATE_TEST_CASE("matrix engine", "[fft]",
		engines::ntt<modnum<998244353>>,
		engines::split<modnum<int(1e9)+7>>,
		engines::crt<modnum<int(1e9)+7>>) {
	using IE = TestType;
	// the tracked engines' scale budget admits N = 2 (entries are N-addend sums), and
	// the non-commutative online squarer accumulates two N-addend products per window
	// (scale 2N), exceeding it
	constexpr int N = IE::unit_scale == 0 ? 3 : 2;
	mt19937 mt(Catch::getSeed());
	test_matrix_engine<engines::matrix<IE, N>, IE::unit_scale == 0, N>(mt);
	// the stable variant works at any N (and its online squarer stays at scale 2)
	test_matrix_engine<engines::matrix_stable<IE, 3>, true, 3>(mt);
}

template <typename E, typename num, int N>
void test_trunc_series_engine(mt19937& mt) {
	using P = typename E::value_type;
	auto rnd_p = [&]() {
		P p;
		for (int i = 0; i < N; i++) p[i] = rnd_val<num>(mt);
		return p;
	};
	for (int la : {1, 2, 3, 17, 33}) {
		for (int lb : {1, 2, 16, 17}) {
			vector<P> a(la), b(lb);
			for (P& p : a) p = rnd_p();
			for (P& p : b) p = rnd_p();
			INFO("la = " << la << ", lb = " << lb);
			REQUIRE(multiply<E>(a, b) == multiply_slow(a, b));
		}
	}
}

TEMPLATE_TEST_CASE("trunc_series engine", "[fft]",
		engines::ntt<modnum<998244353>>,
		engines::split<modnum<int(1e9)+7>>,
		engines::crt<modnum<int(1e9)+7>>) {
	using IE = TestType;
	using num = typename IE::value_type;
	constexpr int N = IE::unit_scale == 0 ? 3 : 2;
	mt19937 mt(Catch::getSeed());
	test_trunc_series_engine<engines::trunc<IE, N>, num, N>(mt);
	test_trunc_series_engine<engines::trunc_stable<IE, 3>, num, 3>(mt);
}

}} // namespace ecnerwala::fft
#include <bits/stdc++.h>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_get_random_seed.hpp>
#line 5 "src/fft/engines/algebras.test.cpp"

#line 2 "src/fft/engines/algebras.hpp"

#line 8 "src/fft/engines/algebras.hpp"

#line 2 "src/fft/engine.hpp"

#line 7 "src/fft/engine.hpp"

#line 2 "src/fft/common.hpp"

#line 8 "src/fft/common.hpp"

/**
 * Author: Andrew He
 * Source: http://neerc.ifmo.ru/trains/toulouse/2017/fft2.pdf
 * Papers about accuracy: http://www.daemonology.net/papers/fft.pdf, http://www.cs.berkeley.edu/~fateman/papers/fftvsothers.pdf
 * For integers rounding works if $(|a| + |b|)\max(a, b) < \mathtt{\sim} 10^9$, or in theory maybe $10^6$.
 *
 * Abstraction layers:
 *   fft_core<num>   FFT itself and other ops on rings with 2^k-th roots of unity. We use bit-reversed indexing in the frequency domain.
 *
 *   engines         Engines for packing/unpacking arbitrary rings for convolution (the `engine` concept).
 *                   Still expose (opaque) transform-domain objects for caching/fusion.
 *
 *   multiply layer  Wrappers for convolving bounded sequences: track length/truncation.
 *
 *   value types     series::vec<E, exact> - R[[x]]
 *                   series::exact<E> - exact (finite-support) power series
 *                   series::trunc<E> - truncated prefix of an (infinite) power series
 *
 *                   polynomials - R[x]. Under x -> 1/x a polynomial becomes a Laurent polynomial in 1/x;
 *                                 shifting by x^{deg P} (reversal) lands it in R[[x]], and we store that exact series.
 *                   poly::vec<E> - polynomial type, supporting natural indexing
 *                   poly::form<E> - finite-support linear forms, via the pairing <P, S> = [x^0] P(1/x) S(x)
 *                                    a linear form is one side of this pairing, applied to the other
 *
 *                   online_multiplier<E> - online (relaxed) multiplication of 2 sequences in n log^2 n time
 *                   ap_sampled_poly<E> - a polynomial stored as its evaluations on an arithmetic progression
 */

namespace ecnerwala {

template<class T> int sz(T&& arg) { using std::size; return int(size(std::forward<T>(arg))); }
inline int nextPow2(int s) { return 1 << (s > 1 ? 32 - __builtin_clz(s-1) : 0); }

namespace fft {

using std::swap;
using std::vector;
using std::min;
using std::max;

// Reusable scratch buffers. Not thread-safe by default: this is deliberately plain
// static storage so single-threaded programs pay no TLS indirection; define
// ECNERWALA_FFT_POOL_STORAGE to `thread_local` for multithreaded use.
#ifndef ECNERWALA_FFT_POOL_STORAGE
#define ECNERWALA_FFT_POOL_STORAGE
#endif
template <typename T> struct buffer_pool {
	static inline ECNERWALA_FFT_POOL_STORAGE std::vector<std::vector<T>> free_list;
	struct handle {
		std::vector<T> v;
		explicit handle(int n) {
			if (!free_list.empty()) {
				v = std::move(free_list.back());
				free_list.pop_back();
			}
			v.assign(n, T());
		}
		handle(const handle&) = delete;
		handle& operator=(const handle&) = delete;
		handle(handle&& o) noexcept : v(std::move(o.v)) {}
		~handle() { if (v.capacity()) free_list.push_back(std::move(v)); }
		T& operator[](int i) { return v[i]; }
		operator std::span<T>() { return std::span<T>(v); }
		std::span<T> span() { return std::span<T>(v); }
	};
	static handle get(int n) { return handle(n); }
};

/* namespace fft */ }

/* namespace ecnerwala */ }
#line 9 "src/fft/engine.hpp"

namespace ecnerwala::fft {

// ==== engine concept ====

// Output operations for the finish step to express arbitrary fusion into the output buffer.
struct assign_op { template <typename T> void operator()(T& d, T v) const { d = v; } };
struct add_op { template <typename T> void operator()(T& d, T v) const { d += v; } };
struct sub_op { template <typename T> void operator()(T& d, T v) const { d -= v; } };
struct add_twice_op { template <typename T> void operator()(T& d, T v) const { d += v + v; } };

// `engine` contract
//   engine represents a way of packing/unpacking sequences over an arbitrary ring into FFT-style transforms.
//   We expect transforms/products of transforms to be linear but potentially lossy/imprecise, so we'll track precision
//   at compile-time as a template parameter.
//
//   E::value_type   The ring we operate over
//   E::unit_scale   0 or 1 depending on whether there's error that can accumulate
//   E::commutative  A marker for whether the ring is commutative
//
//   transformed_t<A>  The transform of a sequence. This object owns its data buffer.
//   product_t<A>      The product of 2 transforms. May equal transformed_t, particularly when unit_scale = 0.
//
//   transformed       alias for transformed_t<unit_scale>
//   product           alias for product_t<unit_scale>
//
//   The basic multiplication API is
//      transform(span<const value_type> in, int n) -> transformed_t<unit_scale>
//      mul(transformed_t<A>, transformed_t<B>, int n) -> product_t<A*B>
//      mul2(a1, b1, a2, b2, int n) -> product_t<A1*B1 + A2*B2>, computing a1*b1 + a2*b2 in one pass
//      finish(product_t<A>, span<value_type>& out, Op) -> void
//
//   Input span can be length up to 2n.
//   Output spans can be length up to n; only the prefix that exists is filled.
//   finish applies Op exactly once per out element, in index order, to
//   value_type targets (so ops may be stateful).
//   Transforms can be longer than necessary, and only the relevant prefix is used.
//
//   For non-exact engines, there's some subtlety in whether we wrap before or after packing.
//   We will choose to wrap *after* packing, which hurts error bounds but makes the prefix condition more uniform.
//
//   Additionally, we have APIs to take advantage of linearity in both transformed and product space:
//      add(transformed_t<A>, transformed_t<B>) -> transformed_t<A+B>
//      add(product_t<K1>, product_t<K2>) -> product_t<K1+K2>
//
//   Finally, we expose some additional fast-transform optimization paths.
//   extend_to only operates on transformed_t<unit_scale>; the others are scale-generic.
//   downsample is also defined on product_t (halving before finish saves inverse-transform work).
//      extend_to       build (if empty) or grow a transform to size m by repeated doubling; feed the same coefficients (sz <= 2m) every time
//                      (each doubling step reads only the coefficients that fit, so zero-padded buffers are fine:
//                       coefficients past twice the existing transform's size must be zero, or it couldn't be a prefix)
//      downsample      compute the half-sized transform/product of just the even (odd = false) or odd terms of the input
//      negate_arg      size n transform of A(-x)
template <typename E>
concept engine = requires(
	std::span<const typename E::value_type> in,
	std::span<typename E::value_type> out,
	typename E::transformed& t,
	const typename E::transformed& ct,
	typename E::product& p,
	const typename E::product& cp,
	int n
) {
	typename E::value_type;
	{ E::transform(in, n) } -> std::same_as<typename E::transformed>;
	{ ct.size() } -> std::same_as<int>;
	E::extend_to(t, n, in);
	{ E::downsample(ct, n, false) } -> std::same_as<typename E::transformed>;
	{ E::downsample(cp, n, false) } -> std::same_as<typename E::product>;
	{ E::negate_arg(ct, n) } -> std::same_as<typename E::transformed>;
	{ E::mul(ct, ct, n) } -> std::same_as<typename E::product>;
	{ E::sq(ct, n) } -> std::same_as<typename E::product>;
	{ E::mul2(ct, ct, ct, ct, n) } -> std::same_as<typename E::template product_t<2 * E::unit_scale>>;
	E::finish(std::move(p), out);
	E::finish(std::move(p), out, add_op{});
	E::finish(E::add(std::move(p), std::move(p)), out);
	{ E::add(E::transform(in, n), ct) } -> std::same_as<typename E::template transformed_t<2 * E::unit_scale>>;
	{ E::add(std::move(p), std::move(p)) } -> std::same_as<typename E::template product_t<2 * E::unit_scale>>;
	requires std::same_as<std::remove_cvref_t<decltype(E::commutative)>, bool>;
	requires std::same_as<std::remove_cvref_t<decltype(E::unit_scale)>, int>;
};

// Constrains two engine-parameterized value types to share the same engine.
template <typename A, typename B>
concept same_engine = std::same_as<typename A::engine_t, typename B::engine_t>;

// short spelling for E::transformed at use sites
template <engine E> using transformed = typename E::transformed;

/* namespace ecnerwala::fft */ }
#line 10 "src/fft/engines/algebras.hpp"

namespace ecnerwala::fft::engines {

// Small NxN matrix over num, row-major
// ==== wrapper engines ====

template <typename num, int N> struct mat {
	std::array<num, size_t(N) * N> a{};
	num& operator[](std::array<int, 2> rc) { return a[size_t(rc[0]) * N + rc[1]]; }
	const num& operator[](std::array<int, 2> rc) const { return a[size_t(rc[0]) * N + rc[1]]; }
	num* data() { return a.data(); }
	const num* data() const { return a.data(); }
	mat& operator+=(const mat& o) { for (int i = 0; i < N*N; i++) a[i] += o.a[i]; return *this; }
	friend mat operator+(mat x, const mat& y) { x += y; return x; }
	mat& operator-=(const mat& o) { for (int i = 0; i < N*N; i++) a[i] -= o.a[i]; return *this; }
	friend mat operator-(mat x, const mat& y) { x -= y; return x; }
	friend mat operator*(const mat& x, const mat& y) {
		mat r;
		for (int i = 0; i < N; i++) for (int k = 0; k < N; k++) for (int j = 0; j < N; j++)
			r[{i, j}] += x[{i, k}] * y[{k, j}];
		return r;
	}
	mat& operator*=(const mat& o) { return *this = *this * o; }
	friend bool operator==(const mat&, const mat&) = default;
};

// Truncated polynomial mod x^N over num
template <typename num, int N> struct trunc_series {
	std::array<num, size_t(N)> a{};
	num& operator[](int i) { return a[size_t(i)]; }
	const num& operator[](int i) const { return a[size_t(i)]; }
	num* data() { return a.data(); }
	const num* data() const { return a.data(); }
	trunc_series& operator+=(const trunc_series& o) { for (int i = 0; i < N; i++) a[i] += o.a[i]; return *this; }
	friend trunc_series operator+(trunc_series x, const trunc_series& y) { x += y; return x; }
	trunc_series& operator-=(const trunc_series& o) { for (int i = 0; i < N; i++) a[i] -= o.a[i]; return *this; }
	friend trunc_series operator-(trunc_series x, const trunc_series& y) { x -= y; return x; }
	friend trunc_series operator*(const trunc_series& x, const trunc_series& y) {
		trunc_series r;
		for (int i = 0; i < N; i++) for (int j = 0; j < N - i; j++) r[i + j] += x[i] * y[j];
		return r;
	}
	trunc_series& operator*=(const trunc_series& o) { return *this = *this * o; }
	friend bool operator==(const trunc_series&, const trunc_series&) = default;
};

// componentwise

// These are "componentwise engines" which model free modules/algebras over the underlying ring.
// Matrices are the canonical example: we can take entry-wise transforms, then multiply/add in transformed space.
//
// We start with a shared `componentwise` base class which handles all linear ops, i.e. not mul.
//
// If the underlying has unit_scale = 1, we may need to avoid accumulation of sums in transformed space;
// then, the product and the transformed data may have different dimensions.
// We'll represent this by an array Ofs of prefix offsets mapping each input/transform-space dimension to a range of product-space dimensions.
// Specifically, out[c] = sum prod[Ofs[c]:Ofs[c+1]]

template <int L> constexpr std::array<int, size_t(L) + 1> componentwise_iota = [] {
	std::array<int, size_t(L) + 1> r{};
	for (int i = 0; i <= L; i++) r[size_t(i)] = i;
	return r;
}();

template <engine E, typename V, int L, std::array<int, size_t(L) + 1> Ofs = componentwise_iota<L>>
struct componentwise {
	using S = typename E::value_type;
	using value_type = V;
	static constexpr int P = Ofs[size_t(L)];  // total product components
	static constexpr int unit_scale = E::unit_scale;
	template <int A = unit_scale> struct transformed_t {
		std::array<typename E::template transformed_t<A>, size_t(L)> t;
		int size() const { return t[0].size(); }
		transformed_t() = default;
		template <int A2> requires (A2 != A) explicit(A2 > A) transformed_t(transformed_t<A2>&& o) {
			for (int c = 0; c < L; c++)
				t[c] = typename E::template transformed_t<A>(std::move(o.t[c]));
		}
	};
	using transformed = transformed_t<>;
	// TODO: if E::product_t == E::transformed_t, mirror that here
	template <int K> struct product_t {
		std::array<typename E::template product_t<K>, size_t(P)> t;
		int size() const { return t[0].size(); }
		product_t() = default;
		template <int K2> requires (K2 != K) explicit(K2 > K) product_t(product_t<K2>&& o) {
			for (int c = 0; c < P; c++)
				t[c] = typename E::template product_t<K>(std::move(o.t[c]));
		}
	};

	static transformed transform(std::span<const V> a, int n) {
		transformed r;
		auto buf = buffer_pool<S>::get(sz(a));
		for (int c = 0; c < L; c++) {
			for (int i = 0; i < sz(a); i++) buf[i] = a[i].data()[c];
			r.t[c] = E::transform(std::span<const S>(buf.span()), n);
		}
		return r;
	}
	static void extend_to(transformed& t, int m, std::span<const V> coeffs) {
		if (t.size() >= m) return;
		auto buf = buffer_pool<S>::get(sz(coeffs));
		for (int c = 0; c < L; c++) {
			for (int i = 0; i < sz(coeffs); i++) buf[i] = coeffs[i].data()[c];
			E::extend_to(t.t[c], m, std::span<const S>(buf.span()));
		}
	}
	template <int A> static transformed_t<A> downsample(const transformed_t<A>& t, int n, bool odd) {
		transformed_t<A> r;
		for (int c = 0; c < L; c++) r.t[c] = E::downsample(t.t[c], n, odd);
		return r;
	}
	template <int K> static product_t<K> downsample(const product_t<K>& p, int n, bool odd) {
		product_t<K> r;
		for (int c = 0; c < P; c++) r.t[c] = E::downsample(p.t[c], n, odd);
		return r;
	}
	template <int A> static transformed_t<A> negate_arg(const transformed_t<A>& t, int n) {
		transformed_t<A> r;
		for (int c = 0; c < L; c++) r.t[c] = E::negate_arg(t.t[c], n);
		return r;
	}
	template <int A, int B> static transformed_t<A + B> add(transformed_t<A>&& a, const transformed_t<B>& b) {
		transformed_t<A + B> r;
		for (int c = 0; c < L; c++) r.t[c] = E::add(std::move(a.t[c]), b.t[c]);
		return r;
	}
	template <int K1, int K2> static product_t<K1 + K2> add(product_t<K1>&& a, product_t<K2>&& b) {
		product_t<K1 + K2> r;
		for (int c = 0; c < P; c++) r.t[c] = E::add(std::move(a.t[c]), std::move(b.t[c]));
		return r;
	}
	template <int K, typename Op = assign_op> static void finish(product_t<K>&& p, std::span<V> out, Op op = {}) {
		auto buf = buffer_pool<S>::get(sz(out));
		auto emit = [&](std::span<V> dst) {
			for (int c = 0; c < L; c++) {
				E::finish(std::move(p.t[Ofs[size_t(c)]]), buf.span());
				for (int j = Ofs[size_t(c)] + 1; j < Ofs[size_t(c) + 1]; j++)
					E::finish(std::move(p.t[j]), buf.span(), add_op{});
				for (int i = 0; i < sz(dst); i++) dst[i].data()[c] = buf[i];
			}
		};
		// Op must see each out element whole, exactly once, so compose
		// non-assign ops through an element buffer.
		if constexpr (std::same_as<Op, assign_op>) {
			emit(out);
		} else {
			auto vbuf = buffer_pool<V>::get(sz(out));
			emit(vbuf.span());
			for (int i = 0; i < sz(out); i++) op(out[i], vbuf.span()[i]);
		}
	}
};

// Convolve mat<N> (NxN matrices), with accumulation in product space
template <engine E, int N>
struct matrix : componentwise<E, mat<typename E::value_type, N>, N * N> {
	using base = componentwise<E, mat<typename E::value_type, N>, N * N>;
	static constexpr bool commutative = false;
	static constexpr int unit_scale = base::unit_scale;
	template <int A = unit_scale> using transformed_t = typename base::template transformed_t<A>;
	template <int K> using product_t = typename base::template product_t<K * N>;
	using transformed = typename base::transformed;
	using product = product_t<unit_scale * unit_scale>;

	// right fold over k so a tracked inner engine's per-addend types line up
	template <int A, int B, int k = 0>
	static auto entry(const transformed_t<A>& a, const transformed_t<B>& b, int r, int c, int n) {
		auto e = E::mul(a.t[size_t(r) * N + k], b.t[size_t(k) * N + c], n);
		if constexpr (k + 1 == N) return e;
		else return E::add(std::move(e), entry<A, B, k + 1>(a, b, r, c, n));
	}
	template <int A, int B>
	static product_t<A * B> mul(const transformed_t<A>& a, const transformed_t<B>& b, int n) {
		product_t<A * B> p;
		for (int r = 0; r < N; r++) for (int c = 0; c < N; c++)
			p.t[size_t(r) * N + c] = entry<A, B>(a, b, r, c, n);
		return p;
	}
	template <int A> static auto sq(const transformed_t<A>& a, int n) { return mul(a, a, n); }
	template <int A1, int B1, int A2, int B2, int k = 0>
	static auto entry2(
		const transformed_t<A1>& a1, const transformed_t<B1>& b1,
		const transformed_t<A2>& a2, const transformed_t<B2>& b2,
		int r, int c, int n
	) {
		auto e = E::mul2(
			a1.t[size_t(r) * N + k], b1.t[size_t(k) * N + c],
			a2.t[size_t(r) * N + k], b2.t[size_t(k) * N + c],
			n
		);
		if constexpr (k + 1 == N) return e;
		else return E::add(std::move(e), entry2<A1, B1, A2, B2, k + 1>(a1, b1, a2, b2, r, c, n));
	}
	template <int A1, int B1, int A2, int B2>
	static product_t<A1 * B1 + A2 * B2> mul2(
		const transformed_t<A1>& a1, const transformed_t<B1>& b1,
		const transformed_t<A2>& a2, const transformed_t<B2>& b2,
		int n
	) {
		product_t<A1 * B1 + A2 * B2> p;
		for (int r = 0; r < N; r++) for (int c = 0; c < N; c++)
			p.t[size_t(r) * N + c] = entry2<A1, B1, A2, B2>(a1, b1, a2, b2, r, c, n);
		return p;
	}
};

// Convolve trunc_series<num, N> (power series truncated at N), with accumulation in product space
template <engine E, int N>
struct trunc : componentwise<E, trunc_series<typename E::value_type, N>, N> {
	using base = componentwise<E, trunc_series<typename E::value_type, N>, N>;
	static constexpr bool commutative = E::commutative;
	static constexpr int unit_scale = base::unit_scale;
	template <int A = unit_scale> using transformed_t = typename base::template transformed_t<A>;
	template <int K> using product_t = typename base::template product_t<K * N>;
	using transformed = typename base::transformed;
	using product = product_t<unit_scale * unit_scale>;

	template <int A, int B, int s, int i = 0>
	static auto entry(const transformed_t<A>& a, const transformed_t<B>& b, int n) {
		auto e = E::mul(a.t[size_t(i)], b.t[size_t(s - i)], n);
		if constexpr (i == s) return e;
		else return E::add(std::move(e), entry<A, B, s, i + 1>(a, b, n));
	}
	template <int A, int B>
	static product_t<A * B> mul(const transformed_t<A>& a, const transformed_t<B>& b, int n) {
		product_t<A * B> p;
		[&]<size_t... s_>(std::index_sequence<s_...>) {
			((p.t[s_] = entry<A, B, int(s_)>(a, b, n)), ...);
		}(std::make_index_sequence<size_t(N)>{});
		return p;
	}
	template <int A> static auto sq(const transformed_t<A>& a, int n) { return mul(a, a, n); }
	template <int A1, int B1, int A2, int B2, int s, int i = 0>
	static auto entry2(
		const transformed_t<A1>& a1, const transformed_t<B1>& b1,
		const transformed_t<A2>& a2, const transformed_t<B2>& b2,
		int n
	) {
		auto e = E::mul2(a1.t[size_t(i)], b1.t[size_t(s - i)], a2.t[size_t(i)], b2.t[size_t(s - i)], n);
		if constexpr (i == s) return e;
		else return E::add(std::move(e), entry2<A1, B1, A2, B2, s, i + 1>(a1, b1, a2, b2, n));
	}
	template <int A1, int B1, int A2, int B2>
	static product_t<A1 * B1 + A2 * B2> mul2(
		const transformed_t<A1>& a1, const transformed_t<B1>& b1,
		const transformed_t<A2>& a2, const transformed_t<B2>& b2,
		int n
	) {
		product_t<A1 * B1 + A2 * B2> p;
		[&]<size_t... s_>(std::index_sequence<s_...>) {
			((p.t[s_] = entry2<A1, B1, A2, B2, int(s_)>(a1, b1, a2, b2, n)), ...);
		}(std::make_index_sequence<size_t(N)>{});
		return p;
	}
};

// Stable variants of the wrapper engines: do not accumulate in product space.
// This costs an extra log factor.

template <int N> constexpr std::array<int, size_t(N) * N + 1> matrix_stable_ofs = [] {
	std::array<int, size_t(N) * N + 1> r{};
	for (int i = 0; i <= N * N; i++) r[size_t(i)] = i * N;
	return r;
}();

template <engine E, int N>
struct matrix_stable
		: componentwise<E, mat<typename E::value_type, N>, N * N, matrix_stable_ofs<N>> {
	using base = componentwise<E, mat<typename E::value_type, N>, N * N, matrix_stable_ofs<N>>;
	static constexpr bool commutative = false;
	static constexpr int unit_scale = base::unit_scale;
	template <int A = unit_scale> using transformed_t = typename base::template transformed_t<A>;
	template <int K> using product_t = typename base::template product_t<K>;
	using transformed = typename base::transformed;
	using product = product_t<unit_scale * unit_scale>;

	template <int A, int B>
	static product_t<A * B> mul(const transformed_t<A>& a, const transformed_t<B>& b, int n) {
		product_t<A * B> p;
		// entry (r, c)'s k-th addend a(r,k)*b(k,c), grouped per the offsets
		for (int r = 0; r < N; r++) for (int c = 0; c < N; c++) for (int k = 0; k < N; k++)
			p.t[(size_t(r) * N + c) * N + k] = E::mul(a.t[size_t(r) * N + k], b.t[size_t(k) * N + c], n);
		return p;
	}
	template <int A> static auto sq(const transformed_t<A>& a, int n) { return mul(a, a, n); }
	template <int A1, int B1, int A2, int B2>
	static product_t<A1 * B1 + A2 * B2> mul2(
		const transformed_t<A1>& a1, const transformed_t<B1>& b1,
		const transformed_t<A2>& a2, const transformed_t<B2>& b2,
		int n
	) {
		product_t<A1 * B1 + A2 * B2> p;
		for (int r = 0; r < N; r++) for (int c = 0; c < N; c++) for (int k = 0; k < N; k++)
			p.t[(size_t(r) * N + c) * N + k] = E::mul2(
				a1.t[size_t(r) * N + k], b1.t[size_t(k) * N + c],
				a2.t[size_t(r) * N + k], b2.t[size_t(k) * N + c],
				n
			);
		return p;
	}
};

template <int N> constexpr std::array<int, size_t(N) + 1> trunc_series_stable_ofs = [] {
	std::array<int, size_t(N) + 1> r{};
	for (int i = 0; i <= N; i++) r[size_t(i)] = i * (i + 1) / 2;
	return r;
}();

template <engine E, int N>
struct trunc_stable
		: componentwise<E, trunc_series<typename E::value_type, N>, N, trunc_series_stable_ofs<N>> {
	using base = componentwise<E, trunc_series<typename E::value_type, N>, N, trunc_series_stable_ofs<N>>;
	static constexpr bool commutative = E::commutative;
	static constexpr int unit_scale = base::unit_scale;
	template <int A = unit_scale> using transformed_t = typename base::template transformed_t<A>;
	template <int K> using product_t = typename base::template product_t<K>;
	using transformed = typename base::transformed;
	using product = product_t<unit_scale * unit_scale>;

	template <int A, int B>
	static product_t<A * B> mul(const transformed_t<A>& a, const transformed_t<B>& b, int n) {
		product_t<A * B> p;
		for (int s = 0; s < N; s++) for (int i = 0; i <= s; i++)
			p.t[size_t(trunc_series_stable_ofs<N>[size_t(s)] + i)] = E::mul(a.t[size_t(i)], b.t[size_t(s - i)], n);
		return p;
	}
	template <int A> static auto sq(const transformed_t<A>& a, int n) { return mul(a, a, n); }
	template <int A1, int B1, int A2, int B2>
	static product_t<A1 * B1 + A2 * B2> mul2(
		const transformed_t<A1>& a1, const transformed_t<B1>& b1,
		const transformed_t<A2>& a2, const transformed_t<B2>& b2,
		int n
	) {
		product_t<A1 * B1 + A2 * B2> p;
		for (int s = 0; s < N; s++) for (int i = 0; i <= s; i++)
			p.t[size_t(trunc_series_stable_ofs<N>[size_t(s)] + i)] = E::mul2(
				a1.t[size_t(i)], b1.t[size_t(s - i)],
				a2.t[size_t(i)], b2.t[size_t(s - i)],
				n
			);
		return p;
	}
};

/* namespace ecnerwala::fft::engines */ }
#line 2 "src/fft/engines/ntt.hpp"

#line 8 "src/fft/engines/ntt.hpp"

#line 2 "src/fft/core.hpp"

#line 8 "src/fft/core.hpp"

#line 2 "src/modnum.hpp"

#line 9 "src/modnum.hpp"

template <typename T> T mod_inv_in_range(T a, T m) {
	// assert(0 <= a && a < m);
	T x = a, y = m;
	// abs coeff of a in x and y (they're always opposite sign)
	T vx = 1, vy = 0;
	bool swap = false;
	while (x) {
		T k = y / x;
		y %= x;
		vy += k * vx;
		std::swap(x, y);
		std::swap(vx, vy);
		swap ^= 1;
	}
	assert(y == 1);
	return swap ? vy : m - vy;
}

template <typename T> struct extended_gcd_result {
	T gcd;
	T coeff_a, coeff_b;
};
template <typename T> extended_gcd_result<T> extended_gcd(T a, T b) {
	T x = a, y = b;
	// coeff of a and b in x and y
	T ax = 1, ay = 0;
	T bx = 0, by = 1;
	while (x) {
		T k = y / x;
		y %= x;
		ay -= k * ax;
		by -= k * bx;
		std::swap(x, y);
		std::swap(ax, ay);
		std::swap(bx, by);
	}
	return {y, ay, by};
}

template <typename T> T mod_inv(T a, T m) {
	a %= m;
	a = a < 0 ? a + m : a;
	return mod_inv_in_range(a, m);
}

// Derives the boilerplate operator surface of a number type from its compound
// ops, ==, neg(), and inv().
// Bodies are only instantiated on use, so a type may omit some of the
// underlying pieces if the corresponding derived ops are never called.
template <typename Self>
struct num_ops {
	Self operator+ () const { return static_cast<const Self&>(*this); }
	Self operator- () const { return static_cast<const Self&>(*this).neg(); }

	friend Self operator ++ (Self& a, int) { Self r = a; ++a; return r; }
	friend Self operator -- (Self& a, int) { Self r = a; --a; return r; }
	friend Self operator + (const Self& a, const Self& b) { return Self(a) += b; }
	friend Self operator - (const Self& a, const Self& b) { return Self(a) -= b; }
	friend Self operator * (const Self& a, const Self& b) { return Self(a) *= b; }
	friend Self operator / (const Self& a, const Self& b) { return Self(a) /= b; }

	friend bool operator != (const Self& a, const Self& b) { return !(a == b); }

	friend Self neg(const Self& a) { return a.neg(); }
	friend Self inv(const Self& a) { return a.inv(); }
};

// Storage and arithmetic for numbers mod Self::MOD, as a reduced
// representative v in [0, MOD) of unsigned type V.
// The type provides static MOD (of type V), reduce (value -> representative),
// and *=;
// everything else is derived here, valid for any MOD up to V's full range
// (sums and differences are tracked mod 2^bits, so no headroom is needed).
// Hooks may be overridden in the type's own body (e.g. a faster += / -=).
template <typename Self, typename V>
struct mod_ops : num_ops<Self> {
	static_assert(std::unsigned_integral<V>);
	V v;

	struct is_reduced_tag {};

	mod_ops() : v(0) {}
	mod_ops(V v_, is_reduced_tag) : v(v_) { assert(v < Self::MOD); }
	template <std::integral I> mod_ops(I x) : v(Self::reduce(x)) {}

	static Self from_reduced(V v) { return Self(v, is_reduced_tag{}); }

	// A negative value reduces via its nonnegative complement: x = -1 - ~x.
	static V reduce(std::signed_integral auto x) {
		using U = std::make_unsigned_t<decltype(x)>;
		return x < 0 ? V(Self::MOD - 1 - Self::reduce(U(~x))) : Self::reduce(U(x));
	}

	explicit operator V() const { return v; }
	std::make_signed_t<V> balanced() const {
		return std::make_signed_t<V>(Self::MOD-v > v ? v : v - Self::MOD);
	}

	friend bool operator == (const Self& a, const Self& b) { return a.v == b.v; }
	friend std::ostream& operator << (std::ostream& out, const Self& n) { return out << n.v; }
	friend std::istream& operator >> (std::istream& in, Self& n) { int64_t v_; in >> v_; n = Self(v_); return in; }

	Self& operator ++ () {
		++v;
		if (v == Self::MOD) v = 0;
		return self();
	}
	Self& operator -- () {
		if (v == 0) v = Self::MOD;
		--v;
		return self();
	}
	Self& operator += (const Self& o) { v = Self::sub_mod_raw(v, Self::MOD - o.v); return self(); }
	Self& operator -= (const Self& o) { v = Self::sub_mod_raw(v, o.v); return self(); }
	Self& operator /= (const Self& o) { return self() *= o.inv(); }

	// Returns a - b mod MOD, for b in [0, MOD]; wraparound detects the underflow.
	static V sub_mod_raw(V a, V b) { return a < b ? a - b + Self::MOD : a - b; }

	Self neg() const { return from_reduced(v ? Self::MOD - v : 0); }
	Self inv() const { return from_reduced(mod_inv_in_range(v, Self::MOD)); }

private:
	Self& self() { return static_cast<Self&>(*this); }
};

template <auto MOD_> struct modnum : mod_ops<modnum<MOD_>, std::make_unsigned_t<decltype(MOD_)>> {
	using Self = modnum;
	static_assert(MOD_ > 0, "MOD must be positive");
	using V = std::make_unsigned_t<decltype(MOD_)>;
	static constexpr V MOD = V(MOD_);

	using base = mod_ops<modnum, V>;
	using base::base;
	using base::v;
	using base::reduce;

	static V reduce(std::unsigned_integral auto x) { return V(x % MOD); }

	explicit operator std::make_signed_t<V>() const
		requires (MOD <= V(std::numeric_limits<std::make_signed_t<V>>::max()))
	{
		return std::make_signed_t<V>(v);
	}

	Self& operator *= (const Self& o) {
		if constexpr (sizeof(V) <= 4) v = V(uint64_t(v) * o.v % MOD);
		else v = V(__uint128_t(v) * o.v % MOD);
		return *this;
	}
};

struct mod_goldilocks : mod_ops<mod_goldilocks, uint64_t> {
	using Self = mod_goldilocks;
	static constexpr uint64_t MOD = 0xffffffff00000001ull;
	static constexpr uint64_t EPS = -MOD;
	// We have 2^32 is a primitive 6th root of unity.
	// Note that omega_8 + omega_8^7 == 2^24 - 2^72 == sqrt(2)
	// We'll pick the root so that 2^24 - 2^72 is our primitive 384th root of unity.
	static constexpr uint64_t PRIMITIVE_ROOT = 2717;

	using base = mod_ops<mod_goldilocks, uint64_t>;
	using base::base;
	using base::reduce;
	mod_goldilocks() = default;
	mod_goldilocks(__int128_t a) : base(a < 0 ? uint64_t(MOD - 1 - __uint128_t(~a) % MOD) : uint64_t(__uint128_t(a) % MOD), is_reduced_tag{}) {}
	mod_goldilocks(__uint128_t a) : base(uint64_t(a % MOD), is_reduced_tag{}) {}

	// Avoids the division: any uint64_t is within MOD of reduced.
	static uint64_t reduce(std::unsigned_integral auto x) {
		static_assert(sizeof(x) <= 8);
		uint64_t a = x;
		return a >= MOD ? a - MOD : a;
	}

	// returns a-b, assuming -MOD <= a-b, e.g. b <= MOD
	static uint64_t sub_mod_raw(uint64_t a, uint64_t b) {
#if defined(__x86_64__)
		// TODO: We could try to write this using intrinsics, but GCC sometimes produces the wrong code.
		uint64_t res_wrapped = a;
		uint64_t adjustment = b;
		asm (
			// AT&T syntax: SRC DST
			"sub %[y], %[x]\n\t"
			// Trick from plonky2 implementation:
			// After sub, flag CF is set iff we underflowed. We want to correct by EPS == 2^32 - 1 iff C is set.
			// sbb (subtract with borrow) computes DST <- DST - SRC - CF
			// Thus, we can use the 32-bit form of sbb on a dummy register to load CF ? EPS : 0.
			// Here, we'll just reuse the original register holding b.
			"sbb %k[y], %k[y]\n\t"
			: [x] "+r"(res_wrapped),
			[y] "+r"(adjustment)
			:
			: "cc"
		);
#else
		uint64_t res_wrapped = a - b;
		uint64_t adjustment = (res_wrapped > a) ? EPS : 0;
#endif
		return res_wrapped - adjustment;
	}

	// Reduce lo + 2^64 * mi + 2^96 * hi, where hi <= MOD
	static uint64_t reduce_u160_raw(uint64_t lo, uint32_t mi, uint64_t hi) {
		// result = lo - hi + EPS * mi
		// 0 <= lo <= 2^64 - 1 = MOD + EPS - 1
		// 0 <= EPS * mi <= (2^32 - 1) * EPS = MOD - 1 - EPS
		// 0 <= hi <= MOD
		// -MOD <= lo - hi + EPS * mi <= 2*MOD-2
		// so we do have some leeway
		return sub_mod_raw(sub_mod_raw(lo, hi), MOD-(uint64_t(mi)<<32)+mi);
	}

	static uint64_t reduce_u128_raw(__uint128_t v) {
		uint64_t hi = uint64_t(v >> 64);
		uint64_t lo = uint64_t(v);
		uint32_t hi_hi = uint32_t(hi >> 32);
		uint32_t hi_lo = uint32_t(hi);
		return reduce_u160_raw(lo, hi_lo, hi_hi);
	}

	Self& operator *= (Self o) {
		v = reduce_u128_raw(__uint128_t(v) * __uint128_t(o.v));
		return *this;
	}
};

template <typename T> T power(T a, long long b) {
	assert(b >= 0);
	T r = 1; while (b) { if (b & 1) r *= a; b >>= 1; a *= a; } return r;
}

template <typename U, typename V> struct pairnum : num_ops<pairnum<U, V>> {
	using Self = pairnum;
	U u;
	V v;

	pairnum() : u(0), v(0) {}
	pairnum(long long val) : u(val), v(val) {}
	pairnum(const U& u_, const V& v_) : u(u_), v(v_) {}

	friend std::ostream& operator << (std::ostream& out, const Self& n) { return out << '(' << n.u << ',' << ' ' << n.v << ')'; }
	friend std::istream& operator >> (std::istream& in, Self& n) { long long val; in >> val; n = Self(val); return in; }

	friend bool operator == (const Self& a, const Self& b) { return a.u == b.u && a.v == b.v; }

	Self inv() const {
		return Self(u.inv(), v.inv());
	}
	Self neg() const {
		return Self(u.neg(), v.neg());
	}

	Self& operator ++ () {
		++u, ++v;
		return *this;
	}
	Self& operator -- () {
		--u, --v;
		return *this;
	}

	Self& operator += (const Self& o) {
		u += o.u;
		v += o.v;
		return *this;
	}
	Self& operator -= (const Self& o) {
		u -= o.u;
		v -= o.v;
		return *this;
	}
	Self& operator *= (const Self& o) {
		u *= o.u;
		v *= o.v;
		return *this;
	}
	Self& operator /= (const Self& o) {
		u /= o.u;
		v /= o.v;
		return *this;
	}
};

template <typename tag> struct dynamic_modnum : mod_ops<dynamic_modnum<tag>, uint32_t> {
	using Self = dynamic_modnum;
private:
	inline static uint32_t MOD_ = 0;
	inline static uint64_t BARRETT_M = 0;

public:
	// Make only the const-reference public, to force the use of set_mod
	static constexpr uint32_t const& MOD = MOD_;

	using base = mod_ops<dynamic_modnum, uint32_t>;
	using base::base;
	using base::v;
	using base::reduce;

	// Barret reduction taken from KACTL:
	/**
	 * Author: Simon Lindholm
	 * Date: 2020-05-30
	 * License: CC0
	 * Source: https://en.wikipedia.org/wiki/Barrett_reduction
	 * Description: Compute $a \% b$ about 5 times faster than usual, where $b$ is constant but not known at compile time.
	 * Returns a value congruent to $a \pmod b$ in the range $[0, 2b)$.
	 * Status: proven correct, stress-tested
	 * Measured as having 4 times lower latency, and 8 times higher throughput, see stress-test.
	 * Details:
	 * More precisely, it can be proven that the result equals 0 only if $a = 0$,
	 * and otherwise lies in $[1, (1 + a/2^64) * b)$.
	 */
	static void set_mod(int mod) {
		assert(mod > 0);
		MOD_ = uint32_t(mod);
		BARRETT_M = (uint64_t(-1) / MOD);
	}
	static uint32_t barrett_reduce_partial(uint64_t a) {
		return uint32_t(a - uint64_t((__uint128_t(BARRETT_M) * a) >> 64) * MOD);
	}
	static uint32_t barrett_reduce(uint64_t a) {
		int32_t res = int32_t(barrett_reduce_partial(a) - MOD);
		return uint32_t((res < 0) ? res + int32_t(MOD) : res);
	}

	struct mod_reader {
		friend std::istream& operator >> (std::istream& i, mod_reader) {
			int mod; i >> mod;
			Self::set_mod(mod);
			return i;
		}
	};
	static mod_reader MOD_READER() {
		return mod_reader();
	}

	static uint32_t reduce(std::unsigned_integral auto x) {
		static_assert(sizeof(x) <= 8);
		return barrett_reduce(x);
	}

	explicit operator int() const { return int(v); }

	Self& operator *= (const Self& o) {
		v = barrett_reduce(uint64_t(v) * o.v);
		return *this;
	}
};

template <typename T> struct mod_constraint {
	T v, mod;

	friend mod_constraint operator & (mod_constraint a, mod_constraint b) {
		if (a.mod < b.mod) std::swap(a, b);
		if (b.mod == 1) return a;

		extended_gcd_result<T> egcd = extended_gcd<T>(a.mod, b.mod);
		assert(a.v % egcd.gcd == b.v % egcd.gcd);

		T extra = b.v - a.v % b.mod;
		extra /= egcd.gcd;

		extra *= egcd.coeff_a;
		extra %= b.mod / egcd.gcd;
		extra += (extra < 0) ? b.mod / egcd.gcd : 0;

		return mod_constraint{
			a.v + extra * a.mod,
			a.mod * (b.mod / egcd.gcd)
		};
	}
};
#line 11 "src/fft/core.hpp"

namespace ecnerwala::fft {

// ==== core: roots, buffers, raw transforms ====

// Complex
template <typename dbl> struct cplx { /// start-hash
	dbl x, y;
	cplx(dbl x_ = 0, dbl y_ = 0) : x(x_), y(y_) { }
	friend cplx operator+(cplx a, cplx b) { return cplx(a.x + b.x, a.y + b.y); }
	friend cplx operator-(cplx a, cplx b) { return cplx(a.x - b.x, a.y - b.y); }
	friend cplx operator*(cplx a, cplx b) { return cplx(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); }
	friend cplx conj(cplx a) { return cplx(a.x, -a.y); }
	friend cplx inv(cplx a) { dbl n = (a.x*a.x+a.y*a.y); return cplx(a.x/n,-a.y/n); }
};

// getRoot implementations
template <typename num> struct getRoot {
	static num f(int k) = delete;
};
template <typename dbl> struct getRoot<cplx<dbl>> {
	static cplx<dbl> f(int k) {
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
		dbl a=2*M_PI/k;
		return cplx<dbl>(cos(a),sin(a));
	}
};
template <int MOD> struct primitive_root {
	static const int value;
};
// 998244353 = (119 << 23) + 1 = 2^30 - 2^26 - 2^23 + 1
template <> struct primitive_root<998244353> {
	static const int value = 3;
};
// babybear prime
template <> struct primitive_root<(15 << 27) + 1> {
	static const int value = 31;
};
// koalabear prime
template <> struct primitive_root<(127 << 24) + 1> {
	static const int value = 3;
};
template <> struct primitive_root<(7 << 26) + 1> {
	static const int value = 3;
};
template <> struct primitive_root<(5 << 25) + 1> {
	static const int value = 3;
};
template <int MOD> struct getRoot<modnum<MOD>> {
	static modnum<MOD> f(int k) {
		assert((MOD-1)%k == 0);
		return power(modnum<MOD>(primitive_root<MOD>::value), (MOD-1)/k);
	}
};
template <> struct getRoot<mod_goldilocks> {
	static mod_goldilocks f(int k) {
		assert((mod_goldilocks::MOD-1)%k == 0);
		return power(mod_goldilocks(mod_goldilocks::PRIMITIVE_ROOT), (mod_goldilocks::MOD-1)/k);
	}
};

// We take the bit-reverse convention: the coefficient of a[i] -> b[j] is omega^{i * bit_reverse(j)}.
// This means that the size 2^{k-1} transform is the prefix of the size 2^k transform (wrapping the input).
//
// We mostly work with spans here:
//   spans of transforms are expected to have length exactly 2^k
//   spans of inputs/outputs are expected to have length [0, 2^{k+1})
//
// Inputs/outputs are treated mod x^{2^k} - 1.
// Their length is allowed to be bigger than 2^k mostly to perform ops on sequences of size n+1 with only transforms of size n.
// The upper bound of 2^{k+1} is arbitrary: we could tighten it to 2^k + 1 or loosen it to infinity, this is just a "defensive" choice.
template <typename num> struct fft_core {

	static inline vector<int> rev;
	// rt[2^k + i] = 1^{i / 2^(k+1)}
	// TODO: can we get rid of inv_rt; alternatively, should we store inv_rt in bit-reverse order?
	static inline vector<num> rt, inv_rt;

	static void init(int n) {
		if (n <= sz(rt)) return;
		rev.resize(n);
		for (int i = 0; i < n; i++) {
			rev[i] = (rev[i>>1] | ((i&1)*n)) >> 1;
		}
		rt.reserve(n); inv_rt.reserve(n);
		while (sz(rt) < 2 && sz(rt) < n) { rt.push_back(num(1)); inv_rt.push_back(num(1)); }
		for (int k = sz(rt); k < n; k *= 2) {
			rt.resize(2*k); inv_rt.resize(2*k);
			num z = getRoot<num>::f(2*k);
			num iz = inv(z);
			for (int i = k/2; i < k; i++) {
				rt[2*i] = rt[i], rt[2*i+1] = rt[i]*z;
				inv_rt[2*i] = inv_rt[i], inv_rt[2*i+1] = inv_rt[i]*iz;
			}
		}
	}

	// bit-reversal of i as a log2(n)-bit number
	static int brev(int i, int n) {
		int s = __builtin_ctz(unsigned(sz(rev)/n));
		return rev[i] >> s;
	}
	// index of the conjugate evaluation point, in the returned bit-reversed order
	static int conj_index(int j) {
		return j == 0 ? 0 : j ^ ((1 << (31 - __builtin_clz(unsigned(j)))) - 1);
	}

	static void forward(std::span<num> a) {
		int n = sz(a);
		if (n <= 1) return;
		init(n);
		for (int k = n/2; k >= 1; k /= 2) {
			for (int i = 0; i < n; i += 2*k) {
				for (int j = 0; j < k; j++) {
					num u = a[i+j], v = a[i+j+k];
					a[i+j] = u + v;
					a[i+j+k] = (u - v) * rt[j+k];
				}
			}
		}
	}

	static void inverse(std::span<num> a) {
		int n = sz(a);
		if (n <= 1) return;
		init(n);
		for (int k = 1; k < n; k *= 2) {
			for (int i = 0; i < n; i += 2*k) {
				for (int j = 0; j < k; j++) {
					num t = inv_rt[j+k] * a[i+j+k];
					a[i+j+k] = a[i+j] - t;
					a[i+j] = a[i+j] + t;
				}
			}
		}
	}

	// Extend a size 2^{k-1} transform to size 2^k; we need the coefficients.
	// t must have size 2^k, and coeffs must have size at most 2^{k+1}.
	static void extend(std::span<num> t, std::span<const num> coeffs) {
		int n = sz(t) / 2;
		assert(sz(coeffs) <= 2 * n);
		init(sz(t));
		auto b = t.subspan(n, n);
		int lo = min(sz(coeffs), n);
		for (int i = 0; i < lo; i++) {
			// rt[n + i] = w_{2n}^i
			b[i] = coeffs[i] * rt[n + i];
		}
		std::fill(b.begin() + lo, b.end(), num(0));
		for (int i = n; i < sz(coeffs); i++) {
			b[i - n] = b[i - n] - coeffs[i] * rt[i];
		}
		forward(b);
	}

	// Consider t = transform(P) and P(x) = E(x^2) + O(x^2) * x
	// `even_half` and `odd_half` extract a size 2^{k-1} transform of E/O, respectively.
	static void even_half(std::span<const num> t, std::span<num> out) {
		int n = sz(out);
		assert(sz(t) >= 2*n);
		num half = inv(num(2));
		for (int j = 0; j < n; j++) out[j] = (t[2*j] + t[2*j+1]) * half;
	}
	static void odd_half(std::span<const num> t, std::span<num> out) {
		int n = sz(out);
		assert(sz(t) >= 2*n);
		init(2*n);
		num half = inv(num(2));
		for (int j = 0; j < n; j++) {
			// entry j of the size-2n transform pairs (w, -w) with w = w_{2n}^{brev(j, n)}
			out[j] = (t[2*j] - t[2*j+1]) * half * inv_rt[n + brev(j, n)];
		}
	}
};

/* namespace ecnerwala::fft */ }
#line 11 "src/fft/engines/ntt.hpp"

namespace ecnerwala::fft::engines {

template <typename num> struct ntt {
	using value_type = num;
	static constexpr bool commutative = true;
	using core = fft_core<num>;
	struct transformed {
		vector<num> v;
		int size() const { return sz(v); }
	};
	using product = transformed;
	static constexpr int unit_scale = 0;
	template <int A = 0> using transformed_t = transformed;
	template <int K = 0> using product_t = product;

	static transformed transform(std::span<const num> a, int n) {
		assert(sz(a) <= 2 * n);
		transformed r;
		r.v.assign(n, num(0));
		int lo = min(sz(a), n);
		std::copy(a.begin(), a.begin() + lo, r.v.begin());
		for (int i = n; i < sz(a); i++) r.v[i - n] += a[i];
		core::forward(std::span<num>(r.v));
		return r;
	}
	static void extend_to(transformed& t, int m, std::span<const num> coeffs) {
		assert(!(m & (m-1)) && sz(coeffs) <= 2 * m);
		if (t.size() >= m) return;
		if (t.size() == 0) { t = transform(coeffs, m); return; }
		while (t.size() < m) {
			int s = t.size();
			t.v.resize(2 * s);
			// coeffs past 2s are zero: they didn't fit in the transform we're a prefix of
			core::extend(std::span<num>(t.v), coeffs.first(size_t(min(sz(coeffs), 2 * s))));
		}
	}
	static transformed downsample(const transformed& t, int n, bool odd) {
		transformed r; r.v.resize(n);
		if (odd) core::odd_half(std::span<const num>(t.v), std::span<num>(r.v));
		else core::even_half(std::span<const num>(t.v), std::span<num>(r.v));
		return r;
	}
	static transformed negate_arg(const transformed& t, int n) {
		assert(n >= 2 && t.size() >= n);
		transformed r; r.v.resize(n);
		for (int j = 0; j < n; j++) r.v[j] = t.v[j ^ 1];
		return r;
	}
	static product mul(const transformed& a, const transformed& b, int n) {
		assert(a.size() >= n && b.size() >= n);
		product p; p.v.resize(n);
		for (int i = 0; i < n; i++) p.v[i] = a.v[i] * b.v[i];
		return p;
	}
	static product sq(const transformed& a, int n) { return mul(a, a, n); }
	static product mul2(
		const transformed& a1, const transformed& b1,
		const transformed& a2, const transformed& b2,
		int n
	) {
		assert(a1.size() >= n && b1.size() >= n && a2.size() >= n && b2.size() >= n);
		product p; p.v.resize(n);
		for (int i = 0; i < n; i++) p.v[i] = a1.v[i] * b1.v[i] + a2.v[i] * b2.v[i];
		return p;
	}
	static product add(product&& a, const product& b) {
		assert(a.size() == b.size());
		for (int i = 0; i < a.size(); i++) a.v[i] += b.v[i];
		return std::move(a);
	}
	template <typename Op = assign_op> static void finish(product&& p, std::span<num> out, Op op = {}) {
		int n = p.size();
		assert(sz(out) <= n);
		core::inverse(std::span<num>(p.v));
		num d = inv(num(n));
		for (int i = 0; i < sz(out); i++) op(out[i], p.v[i] * d);
	}
};

/* namespace ecnerwala::fft::engines */ }
#line 2 "src/fft/engines/split.hpp"

#line 10 "src/fft/engines/split.hpp"

#line 13 "src/fft/engines/split.hpp"

namespace ecnerwala::fft::engines {

// Multiplies mod `mnum` by splitting values into balanced 15-bit halves (each limb in
// [-2^14, 2^14], from the balanced representative |v| <= MOD/2) packed into one complex
// transform per operand.
template <typename mnum> struct split {
	static_assert(sizeof(decltype(mnum::MOD)) <= 4, "limbs must fit 15 bits");
	using value_type = mnum;
	static constexpr bool commutative = true;
	static constexpr int unit_scale = 1;
	using cnum = cplx<double>;
	using core = fft_core<cnum>;
	template <int A = 1> struct transformed_t {
		vector<cnum> v;
		int size() const { return sz(v); }
		transformed_t() = default;
		explicit transformed_t(vector<cnum>&& v_) : v(std::move(v_)) {}
		template <int A2> requires (A2 != A) explicit(A2 > A) transformed_t(transformed_t<A2>&& o)
			: v(std::move(o.v)) {}
	};
	using transformed = transformed_t<1>;
	template <int K> struct product_t {
		// After finish's inverse transforms: lo = (lo*lo, hi*lo), hi = (lo*hi, hi*hi).
		vector<cnum> lo, hi;
		int size() const { return sz(lo); }
		product_t() = default;
		product_t(vector<cnum>&& lo_, vector<cnum>&& hi_) : lo(std::move(lo_)), hi(std::move(hi_)) {}
		template <int K2> requires (K2 != K) explicit(K2 > K) product_t(product_t<K2>&& o)
			: lo(std::move(o.lo)), hi(std::move(o.hi)) {}
	};
	using product = product_t<1>;

	static cnum pack(mnum x) {
		int64_t v = x.balanced();
		int64_t hi = (v + (1 << 14)) >> 15;
		return cnum(double(v - (hi << 15)), double(hi));
	}

	static transformed transform(std::span<const mnum> a, int n) {
		assert(sz(a) <= 2 * n);
		transformed r;
		r.v.assign(n, cnum(0));
		for (int i = 0; i < sz(a); i++) {
			int j = i < n ? i : i - n;
			r.v[j] = r.v[j] + pack(a[i]);
		}
		core::forward(std::span<cnum>(r.v));
		return r;
	}
	static void extend_to(transformed& t, int m, std::span<const mnum> coeffs) {
		assert(!(m & (m-1)) && sz(coeffs) <= 2 * m);
		if (t.size() >= m) return;
		if (t.size() == 0) { t = transform(coeffs, m); return; }
		auto buf = buffer_pool<cnum>::get(sz(coeffs));
		for (int i = 0; i < sz(coeffs); i++) buf[i] = pack(coeffs[i]);
		while (t.size() < m) {
			int s = t.size();
			t.v.resize(2 * s);
			// coeffs past 2s are zero: they didn't fit in the transform we're a prefix of
			core::extend(
				std::span<cnum>(t.v),
				std::span<const cnum>(buf.span()).first(size_t(min(sz(coeffs), 2 * s)))
			);
		}
	}
	static void downsample_core(std::span<const cnum> in, std::span<cnum> out, bool odd) {
		if (odd) core::odd_half(in, out);
		else core::even_half(in, out);
	}
	template <int A> static transformed_t<A> downsample(const transformed_t<A>& t, int n, bool odd) {
		transformed_t<A> r; r.v.resize(n);
		downsample_core(std::span<const cnum>(t.v), std::span<cnum>(r.v), odd);
		return r;
	}
	template <int K> static product_t<K> downsample(const product_t<K>& p, int n, bool odd) {
		product_t<K> r; r.lo.resize(n); r.hi.resize(n);
		downsample_core(std::span<const cnum>(p.lo), std::span<cnum>(r.lo), odd);
		downsample_core(std::span<const cnum>(p.hi), std::span<cnum>(r.hi), odd);
		return r;
	}
	template <int A> static transformed_t<A> negate_arg(const transformed_t<A>& t, int n) {
		assert(n >= 2 && t.size() >= n);
		transformed_t<A> r; r.v.resize(n);
		for (int j = 0; j < n; j++) r.v[j] = t.v[j ^ 1];
		return r;
	}
	template <int A, int B> static transformed_t<A + B> add(transformed_t<A>&& a, const transformed_t<B>& b) {
		transformed_t<A + B> r{std::move(a.v)};
		add_into(r.v, b.v);
		return r;
	}
	// Unpacks b's transform into transforms of its low/high halves via conjugate
	// symmetry, then multiplies both against a's (still packed) transform. The scale
	// parameter only affects the bookkeeping, so the body is a shared untyped impl.
	static void mul_impl(const vector<cnum>& a, const vector<cnum>& b, vector<cnum>& lo, vector<cnum>& hi, int n, bool acc = false) {
		core::init(n);
		lo.resize(n); hi.resize(n);
		for (int i = 0; i < n; i++) {
			int ci = core::conj_index(i);
			cnum g0 = (b[i] + conj(b[ci])) * cnum(0.5);
			cnum t = (b[i] - conj(b[ci])) * cnum(0.5);
			cnum g1 = cnum(t.y, -t.x);
			if (acc) {
				lo[i] = lo[i] + a[i] * g0;
				hi[i] = hi[i] + a[i] * g1;
			} else {
				lo[i] = a[i] * g0;
				hi[i] = a[i] * g1;
			}
		}
	}
	template <int A, int B> static product_t<A * B> mul(const transformed_t<A>& a, const transformed_t<B>& b, int n) {
		assert(a.size() >= n && b.size() >= n);
		product_t<A * B> p;
		mul_impl(a.v, b.v, p.lo, p.hi, n);
		return p;
	}
	template <int A> static product_t<A * A> sq(const transformed_t<A>& a, int n) { return mul(a, a, n); }
	template <int A1, int B1, int A2, int B2>
	static product_t<A1 * B1 + A2 * B2> mul2(
		const transformed_t<A1>& a1, const transformed_t<B1>& b1,
		const transformed_t<A2>& a2, const transformed_t<B2>& b2,
		int n
	) {
		assert(a1.size() >= n && b1.size() >= n && a2.size() >= n && b2.size() >= n);
		product_t<A1 * B1 + A2 * B2> p;
		mul_impl(a1.v, b1.v, p.lo, p.hi, n);
		mul_impl(a2.v, b2.v, p.lo, p.hi, n, true);
		return p;
	}
	static void add_into(vector<cnum>& a, const vector<cnum>& b) {
		assert(sz(a) == sz(b));
		for (int i = 0; i < sz(a); i++) a[i] = a[i] + b[i];
	}
	template <int K1, int K2> static product_t<K1 + K2> add(product_t<K1>&& a, product_t<K2>&& b) {
		product_t<K1 + K2> r{std::move(a.lo), std::move(a.hi)};
		add_into(r.lo, b.lo);
		add_into(r.hi, b.hi);
		return r;
	}
	template <int K = 1, typename Op = assign_op> static void finish(product_t<K>&& p, std::span<mnum> out, Op op = {}) {
		// The fp error budget is divided by the accumulated scale; K <= 2 is very
		// conservative (balanced limbs already left ~2x headroom at max lengths).
		static_assert(K <= 2, "split: accumulated scale too large");
		int n = p.size();
		assert(sz(out) <= n);
		core::inverse(std::span<cnum>(p.lo));
		core::inverse(std::span<cnum>(p.hi));
		const int64_t m = mnum::MOD;
		double d = 1.0 / double(n);
		// llround + a final wrap so negative half-products (e.g. from negate_arg'd
		// transforms) reconstruct correctly.
		for (int i = 0; i < sz(out); i++) {
			int64_t v = (llround(p.lo[i].x * d)
					+ (llround(p.lo[i].y * d) % m << 15)
					+ (llround(p.hi[i].x * d) % m << 15)
					+ (llround(p.hi[i].y * d) % m << 30)) % m;
			if (v < 0) v += m;
			op(out[i], mnum(v));
		}
	}
};

/* namespace ecnerwala::fft::engines */ }
#line 2 "src/fft/engines/crt.hpp"

#line 7 "src/fft/engines/crt.hpp"

#line 11 "src/fft/engines/crt.hpp"

namespace ecnerwala::fft::engines {

// Multiplies mod `mnum` by running NTTs modulo two FFT-friendly primes and CRT'ing.
// Inputs use balanced representatives (|v| <= MOD/2), so the true integer coefficients
// are bounded by n (MOD/2)^2.
template <typename mnum, typename num1 = mod_goldilocks, typename num2 = modnum<(15 << 27) + 1>>
struct crt {
	static_assert(sizeof(decltype(mnum::MOD)) <= 4, "n (MOD/2)^2 must fit the CRT modulus product");
	using value_type = mnum;
	static constexpr bool commutative = true;
	static constexpr int unit_scale = 1;
	using E1 = ntt<num1>;
	using E2 = ntt<num2>;
	template <int A = 1> struct transformed_t {
		typename E1::transformed t1;
		typename E2::transformed t2;
		int size() const { return t1.size(); }
		transformed_t() = default;
		transformed_t(typename E1::transformed&& t1_, typename E2::transformed&& t2_)
			: t1(std::move(t1_)), t2(std::move(t2_)) {}
		template <int A2> requires (A2 != A) explicit(A2 > A) transformed_t(transformed_t<A2>&& o)
			: t1(std::move(o.t1)), t2(std::move(o.t2)) {}
	};
	using transformed = transformed_t<1>;
	template <int K> struct product_t {
		typename E1::product p1;
		typename E2::product p2;
		int size() const { return sz(p1); }
		product_t() = default;
		product_t(typename E1::product&& p1_, typename E2::product&& p2_)
			: p1(std::move(p1_)), p2(std::move(p2_)) {}
		template <int K2> requires (K2 != K) explicit(K2 > K) product_t(product_t<K2>&& o)
			: p1(std::move(o.p1)), p2(std::move(o.p2)) {}
	};
	using product = product_t<1>;

	static transformed transform(std::span<const mnum> a, int n) {
		assert(sz(a) <= 2 * n);
		auto b1 = buffer_pool<num1>::get(sz(a));
		auto b2 = buffer_pool<num2>::get(sz(a));
		for (int i = 0; i < sz(a); i++) { int64_t v = a[i].balanced(); b1[i] = num1(v); b2[i] = num2(v); }
		return transformed{
			E1::transform(std::span<const num1>(b1.span()), n),
			E2::transform(std::span<const num2>(b2.span()), n),
		};
	}
	static void extend_to(transformed& t, int m, std::span<const mnum> coeffs) {
		if (t.size() >= m) return;
		auto b1 = buffer_pool<num1>::get(sz(coeffs));
		auto b2 = buffer_pool<num2>::get(sz(coeffs));
		for (int i = 0; i < sz(coeffs); i++) { int64_t v = coeffs[i].balanced(); b1[i] = num1(v); b2[i] = num2(v); }
		E1::extend_to(t.t1, m, std::span<const num1>(b1.span()));
		E2::extend_to(t.t2, m, std::span<const num2>(b2.span()));
	}
	template <int A> static transformed_t<A> downsample(const transformed_t<A>& t, int n, bool odd) {
		return transformed_t<A>{E1::downsample(t.t1, n, odd), E2::downsample(t.t2, n, odd)};
	}
	template <int K> static product_t<K> downsample(const product_t<K>& p, int n, bool odd) {
		return product_t<K>{E1::downsample(p.p1, n, odd), E2::downsample(p.p2, n, odd)};
	}
	template <int A> static transformed_t<A> negate_arg(const transformed_t<A>& t, int n) {
		return transformed_t<A>{E1::negate_arg(t.t1, n), E2::negate_arg(t.t2, n)};
	}
	// Exact per prime; the scale tracks the true (integer) coefficient growth.
	template <int A, int B> static transformed_t<A + B> add(transformed_t<A>&& a, const transformed_t<B>& b) {
		return transformed_t<A + B>{E1::add(std::move(a.t1), b.t1), E2::add(std::move(a.t2), b.t2)};
	}
	template <int A, int B> static product_t<A * B> mul(const transformed_t<A>& a, const transformed_t<B>& b, int n) {
		return product_t<A * B>{E1::mul(a.t1, b.t1, n), E2::mul(a.t2, b.t2, n)};
	}
	template <int A> static product_t<A * A> sq(const transformed_t<A>& a, int n) { return mul(a, a, n); }
	template <int A1, int B1, int A2, int B2>
	static product_t<A1 * B1 + A2 * B2> mul2(
		const transformed_t<A1>& a1, const transformed_t<B1>& b1,
		const transformed_t<A2>& a2, const transformed_t<B2>& b2,
		int n
	) {
		return product_t<A1 * B1 + A2 * B2>{
			E1::mul2(a1.t1, b1.t1, a2.t1, b2.t1, n),
			E2::mul2(a1.t2, b1.t2, a2.t2, b2.t2, n),
		};
	}
	template <int K1, int K2> static product_t<K1 + K2> add(product_t<K1>&& a, product_t<K2>&& b) {
		return product_t<K1 + K2>{E1::add(std::move(a.p1), b.p1), E2::add(std::move(a.p2), b.p2)};
	}
	template <int K = 1, typename Op = assign_op> static void finish(product_t<K>&& p, std::span<mnum> out, Op op = {}) {
		// The reconstruction needs |c| < whole/2; balanced inputs bound each addend's
		// true coefficients by n (MOD/2)^2, so the safe length is divided by the
		// accumulated scale. K <= 2 is very conservative (~2^35 even for MOD ~ 2^30).
		static_assert(K <= 2, "crt: accumulated scale too large");
		int n = p.size();
		assert(sz(out) <= n);
		auto o1 = buffer_pool<num1>::get(sz(out));
		auto o2 = buffer_pool<num2>::get(sz(out));
		E1::finish(std::move(p.p1), o1.span());
		E2::finish(std::move(p.p2), o2.span());

		// TODO: Could hardcode these
		num1 inv_n2 = inv(num1(num2::MOD));
		num2 inv_n1 = inv(num2(num1::MOD));
		__int128_t whole = __int128_t(num1::MOD) * __int128_t(num2::MOD);

		mnum m1_mod = mnum(num1::MOD);
		mnum m2_mod = mnum(num2::MOD);
		mnum whole_mod = m1_mod * m2_mod;
		for (int i = 0; i < sz(out); i++) {
			num1 v1 = o1[i] * inv_n2;
			num2 v2 = o2[i] * inv_n1;
			mnum o_mod = mnum(uint64_t(v1)) * m2_mod + mnum(int(v2)) * m1_mod;
			__int128_t o_exact = __int128_t(uint64_t(v1)) * __int128_t(num2::MOD) + __int128_t(int(v2)) * __int128_t(num1::MOD);
			if (o_exact >= whole) { o_exact -= whole; o_mod -= whole_mod; }
			// Balanced representatives: |o| <= whole/2
			if (o_exact > whole / 2) o_mod -= whole_mod;
			op(out[i], o_mod);
		}
	}
};

/* namespace ecnerwala::fft::engines */ }
#line 2 "src/fft/multiply.hpp"

#line 10 "src/fft/multiply.hpp"

#line 12 "src/fft/multiply.hpp"

namespace ecnerwala::fft {

// ==== multiply layer ====
// These are free functions to convolve spans.
//
// The interfaces will typically take input spans, an output span, and an Op representing how to fold the result into the output.
// Output spans may alias one of the input spans.
// Output spans may be shorter than expected; the output will just be truncated.
//
// Some functions may also take E::transformed& objects associated with the input
// spans. These will be lazily filled (see E::extend_to) and used if available.

// Circular convolution mod n (power of 2)
template <engine E, typename Op = assign_op>
void multiply_circular(std::span<const typename E::value_type> a, std::span<const typename E::value_type> b,
		std::span<typename E::value_type> out, int n, Op op = {}) {
	assert(!(n & (n-1)));
	auto ta = E::transform(a, n);
	auto tb = E::transform(b, n);
	E::finish(E::mul(ta, tb, n), out, op);
}

template <engine E, typename Op = assign_op>
void square_circular(std::span<const typename E::value_type> a, std::span<typename E::value_type> out, int n, Op op = {}) {
	assert(!(n & (n-1)));
	auto ta = E::transform(a, n);
	E::finish(E::sq(ta, n), out, op);
}

namespace detail {
// Arrays of length 2^k + 1 are somewhat common, so we will optimize them by
// multiplying mod 2^k, and fixing up the leading coefficient.

// Helpers to detect and perform this optimization.
struct conv_size { int n; bool cut; };
inline conv_size conv_size_for(int s) {
	int n = nextPow2(s);
	bool cut = (n == 2 * (s - 1));
	return {cut ? n / 2 : n, cut};
}

// Call op while lazily applying the correction if necessary
template <typename T, typename Op>
void emit_linear(std::span<T> buf, int n, int s, bool cut, T c0, std::span<T> out, Op op) {
	T cn{};
	if (cut) {
		cn = buf[0] - c0;
		buf[0] = c0;
	}
	int lim = min(sz(out), min(s, n));
	for (int i = 0; i < lim; i++) op(out[i], buf[i]);
	if (cut && sz(out) >= s) op(out[s-1], cn);
}

// Applies op, diverting the wrapped leading coefficient of a cut product:
// out[0] receives c0 and the wraparound term is captured into cn for the
// caller to emit at out[s-1].
template <typename T, typename Op>
struct cut_op {
	Op op;
	T* out0;
	T c0;
	T& cn;
	void operator()(T& x, T v) const {
		if (&x == out0) { cn = v - c0; v = c0; }
		op(x, v);
	}
};

// finish + emit_linear fused: write the finished product directly into out,
// applying the cut correction in place.
template <engine E, typename P, typename Op = assign_op>
void finish_linear(
	P&& p, int n, int s, bool cut,
	typename E::value_type c0, std::span<typename E::value_type> out, Op op = {}
) {
	using T = typename E::value_type;
	if (sz(out) == 0) return;
	int lim = min(sz(out), min(s, n));
	if (!cut) {
		E::finish(std::move(p), out.subspan(0, lim), op);
	} else {
		T cn{};
		E::finish(std::move(p), out.subspan(0, lim), cut_op<T, Op>{op, &out[0], c0, cn});
		if (sz(out) >= s) op(out[s-1], cn);
	}
}

}

template <engine E, typename Op = assign_op>
void multiply(std::span<const typename E::value_type> a, std::span<const typename E::value_type> b,
		std::span<typename E::value_type> out, Op op = {}) {
	using T = typename E::value_type;
	if (sz(a) == 0 || sz(b) == 0) return;
	int s = sz(a) + sz(b) - 1;
	auto [n, cut] = detail::conv_size_for(s);
	T c0 = a[0] * b[0];
	auto buf = buffer_pool<T>::get(n);
	multiply_circular<E>(a, b, buf.span(), n);
	detail::emit_linear<T>(buf.span(), n, s, cut, c0, out, op);
}

template <engine E, typename Op = assign_op>
void multiply(std::span<const typename E::value_type> a, transformed<E>& ta,
		std::span<const typename E::value_type> b, transformed<E>& tb,
		std::span<typename E::value_type> out, Op op = {}) {
	using T = typename E::value_type;
	if (sz(a) == 0 || sz(b) == 0) return;
	int s = sz(a) + sz(b) - 1;
	auto [n, cut] = detail::conv_size_for(s);
	T c0 = a[0] * b[0];
	E::extend_to(ta, n, a);
	E::extend_to(tb, n, b);
	detail::finish_linear<E>(E::mul(ta, tb, n), n, s, cut, c0, out, op);
}

template <engine E, typename Op = assign_op>
void multiply_add2(std::span<const typename E::value_type> a1, transformed<E>& ta1,
		std::span<const typename E::value_type> b1, transformed<E>& tb1,
		std::span<const typename E::value_type> a2, transformed<E>& ta2,
		std::span<const typename E::value_type> b2, transformed<E>& tb2,
		std::span<typename E::value_type> out, Op op = {}) {
	using T = typename E::value_type;
	assert(sz(a1) > 0 && sz(b1) > 0 && sz(a2) > 0 && sz(b2) > 0);
	int s = sz(a1) + sz(b1) - 1;
	assert(sz(a2) + sz(b2) - 1 == s);
	auto [n, cut] = detail::conv_size_for(s);
	T c0 = a1[0] * b1[0] + a2[0] * b2[0];
	E::extend_to(ta1, n, a1); E::extend_to(tb1, n, b1);
	E::extend_to(ta2, n, a2); E::extend_to(tb2, n, b2);
	detail::finish_linear<E>(E::mul2(ta1, tb1, ta2, tb2, n), n, s, cut, c0, out, op);
}

// As multiply_add2, but also outputs the summed pointwise product as a reusable
// transform of the (full-length) result, like multiply_cached.
template <engine E>
void multiply_add2_cached(
		std::span<const typename E::value_type> a1, transformed<E>& ta1,
		std::span<const typename E::value_type> b1, transformed<E>& tb1,
		std::span<const typename E::value_type> a2, transformed<E>& ta2,
		std::span<const typename E::value_type> b2, transformed<E>& tb2,
		std::vector<typename E::value_type>& coeffs, transformed<E>& t) {
	using T = typename E::value_type;
	assert(sz(a1) > 0 && sz(b1) > 0 && sz(a2) > 0 && sz(b2) > 0);
	int s = sz(a1) + sz(b1) - 1;
	assert(sz(a2) + sz(b2) - 1 == s);
	coeffs.assign(size_t(s), T{});
	t = transformed<E>{};
	if constexpr (std::same_as<typename E::product, transformed<E>>) {
		auto [n, cut] = detail::conv_size_for(s);
		T c0 = a1[0] * b1[0] + a2[0] * b2[0];
		E::extend_to(ta1, n, a1); E::extend_to(tb1, n, b1);
		E::extend_to(ta2, n, a2); E::extend_to(tb2, n, b2);
		auto p = E::mul2(ta1, tb1, ta2, tb2, n);
		auto tp = p;
		detail::finish_linear<E>(std::move(p), n, s, cut, c0, std::span<T>(coeffs));
		t = std::move(tp);
	} else {
		multiply_add2<E>(a1, ta1, b1, tb1, a2, ta2, b2, tb2, std::span<T>(coeffs));
	}
}

// This helper also accepts an output transform which will be populated if it is cheap to do so
template <engine E>
void multiply_cached(std::span<const typename E::value_type> a, transformed<E>& ta,
		std::span<const typename E::value_type> b, transformed<E>& tb,
		std::vector<typename E::value_type>& coeffs, transformed<E>& t) {
	using T = typename E::value_type;
	coeffs.assign(size_t(sz(a) && sz(b) ? sz(a) + sz(b) - 1 : 0), T{});
	t = transformed<E>{};
	if (coeffs.empty()) return;
	int s = sz(coeffs);
	if constexpr (std::same_as<typename E::product, transformed<E>>) {
		auto [n, cut] = detail::conv_size_for(s);
		T c0 = a[0] * b[0];
		E::extend_to(ta, n, a);
		E::extend_to(tb, n, b);
		auto p = E::mul(ta, tb, n);
		auto tp = p;
		detail::finish_linear<E>(std::move(p), n, s, cut, c0, std::span<T>(coeffs));
		t = std::move(tp);
	} else {
		multiply<E>(a, ta, b, tb, std::span<T>(coeffs));
	}
}

template <engine E, typename Op = assign_op>
void square(std::span<const typename E::value_type> a, std::span<typename E::value_type> out, Op op = {}) {
	using T = typename E::value_type;
	if (sz(a) == 0) return;
	int s = 2 * sz(a) - 1;
	auto [n, cut] = detail::conv_size_for(s);
	T c0 = a[0] * a[0];
	auto buf = buffer_pool<T>::get(n);
	square_circular<E>(a, buf.span(), n);
	detail::emit_linear<T>(buf.span(), n, s, cut, c0, out, op);
}

template <engine E, typename Op = assign_op>
void square(std::span<const typename E::value_type> a, transformed<E>& ta,
		std::span<typename E::value_type> out, Op op = {}) {
	using T = typename E::value_type;
	if (sz(a) == 0) return;
	int s = 2 * sz(a) - 1;
	auto [n, cut] = detail::conv_size_for(s);
	T c0 = a[0] * a[0];
	E::extend_to(ta, n, a);
	detail::finish_linear<E>(E::sq(ta, n), n, s, cut, c0, out, op);
}

// As square, but also outputs the pointwise product as a reusable transform of
// the result (empty when the engine's product isn't a transform).
template <engine E>
void square_cached(std::span<const typename E::value_type> a, transformed<E>& ta,
		std::vector<typename E::value_type>& coeffs, transformed<E>& t) {
	using T = typename E::value_type;
	coeffs.assign(size_t(sz(a) ? 2 * sz(a) - 1 : 0), T{});
	t = transformed<E>{};
	if (coeffs.empty()) return;
	int s = sz(coeffs);
	if constexpr (std::same_as<typename E::product, transformed<E>>) {
		auto [n, cut] = detail::conv_size_for(s);
		T c0 = a[0] * a[0];
		E::extend_to(ta, n, a);
		auto p = E::sq(ta, n);
		auto tp = p;
		detail::finish_linear<E>(std::move(p), n, s, cut, c0, std::span<T>(coeffs));
		t = std::move(tp);
	} else {
		square<E>(a, ta, std::span<T>(coeffs));
	}
}

template <engine E> vector<typename E::value_type> multiply(
		const vector<typename E::value_type>& a, const vector<typename E::value_type>& b) {
	using T = typename E::value_type;
	if (sz(a) == 0 || sz(b) == 0) return {};
	vector<T> r(sz(a) + sz(b) - 1);
	multiply<E>(std::span<const T>(a), std::span<const T>(b), std::span<T>(r));
	return r;
}

template <engine E> vector<typename E::value_type> square(const vector<typename E::value_type>& a) {
	using T = typename E::value_type;
	if (sz(a) == 0) return {};
	vector<T> r(2 * sz(a) - 1);
	square<E>(std::span<const T>(a), std::span<T>(r));
	return r;
}

namespace detail {
// emit_linear but for middle_product
template <typename T, typename Op>
void emit_middle(std::span<T> buf, bool cut, int la, int lb, T c0, T ctop, std::span<T> out, Op op) {
	int m = la - lb + 1;
	T cn{};
	if (cut) {
		cn = buf[0] - c0; // for lb == 1 these coincide: slot 0 = c_0 + c_n and ctop = c_n
		buf[lb - 1] -= ctop;
	}
	int lim = min(sz(out), cut ? m - 1 : m);
	for (int t = 0; t < lim; t++) op(out[t], buf[lb - 1 + t]);
	if (cut && sz(out) >= m) op(out[m-1], cn);
}
}

// Middle product (the transposed multiplication): takes only coefficients of a * b which include terms from all of b.
// Must have len(a) >= len(b)
template <engine E, typename Op = assign_op>
void middle_product(std::span<const typename E::value_type> a, std::span<const typename E::value_type> b,
		std::span<typename E::value_type> out, Op op = {}) {
	using T = typename E::value_type;
	if (sz(a) == 0 || sz(b) == 0) return;
	assert(sz(a) >= sz(b));
	if (sz(a) == sz(b)) {
		T r{};
		for (int i = 0; i < sz(a); i++) {
			r += a[i] * b[sz(b) - 1 - i];
		}
		if (sz(out) > 0) op(out[0], r);
		return;
	}
	auto [n, cut] = detail::conv_size_for(sz(a));
	auto buf = buffer_pool<T>::get(n);
	multiply_circular<E>(a, b, buf.span(), n);
	detail::emit_middle<T>(buf.span(), cut, sz(a), sz(b),
			a[0] * b[0], a[sz(a) - 1] * b[sz(b) - 1], out, op);
}

// TODO: Let's decide whether to keep vector<> returning forms or not; this
// largely depends on whether we think these functions are a public interface or
// merely convenience for value type implementors.
template <engine E> vector<typename E::value_type> middle_product(
		std::span<const typename E::value_type> a, std::span<const typename E::value_type> b) {
	using T = typename E::value_type;
	if (sz(a) == 0 || sz(b) == 0) return {};
	assert(sz(a) >= sz(b));
	vector<T> r(size_t(sz(a) - sz(b) + 1));
	middle_product<E>(a, b, std::span<T>(r));
	return r;
}

template <engine E, typename Op = assign_op>
void middle_product(std::span<const typename E::value_type> a, transformed<E>& ta,
		std::span<const typename E::value_type> b, transformed<E>& tb,
		std::span<typename E::value_type> out, Op op = {}) {
	using T = typename E::value_type;
	if (sz(a) == 0 || sz(b) == 0) return;
	assert(sz(a) >= sz(b));
	if (sz(a) == sz(b)) {
		T r{};
		for (int i = 0; i < sz(a); i++) {
			r += a[i] * b[sz(b) - 1 - i];
		}
		if (sz(out) > 0) op(out[0], r);
		return;
	}
	auto [n, cut] = detail::conv_size_for(sz(a));
	E::extend_to(ta, n, a);
	E::extend_to(tb, n, b);
	auto buf = buffer_pool<T>::get(n);
	E::finish(E::mul(ta, tb, n), buf.span());
	detail::emit_middle<T>(buf.span(), cut, sz(a), sz(b),
			a[0] * b[0], a[sz(a) - 1] * b[sz(b) - 1], out, op);
}

template <engine E>
vector<typename E::value_type> middle_product(std::span<const typename E::value_type> a, transformed<E>& ta,
		std::span<const typename E::value_type> b, transformed<E>& tb) {
	using T = typename E::value_type;
	if (sz(a) == 0 || sz(b) == 0) return {};
	assert(sz(a) >= sz(b));
	vector<T> r(size_t(sz(a) - sz(b) + 1));
	middle_product<E>(a, ta, b, tb, std::span<T>(r));
	return r;
}

/* namespace ecnerwala::fft */ }
#line 2 "src/fft/online.hpp"

#line 7 "src/fft/online.hpp"

#line 9 "src/fft/online.hpp"

namespace ecnerwala {

// ==== online multiplication ====

// Online (relaxed) multiplication: computes the first N terms of f*g given the terms one at a time.
template <fft::engine E> struct online_multiplier {
	using T = typename E::value_type;
	int N; int i;
	std::vector<T> f, g;
	std::vector<T> res;
	std::vector<fft::transformed<E>> f_blocks, g_blocks; // level k: block [2^k, 2^{k+1})

	online_multiplier(int N_) : N(N_), i(0), f(N, T{}), g(N, T{}), res(2*N+1, T{}) {}

	T peek() {
		return res[i];
	}

	void push(T v_f, T v_g) {
		assert(i < N);
		f[i] = v_f;
		g[i] = v_g;
		if (i == 0) {
			res[0] += v_f * v_g;
		} else {
			res[i] += v_f * g[0];
			res[i] += f[0] * v_g;
			for (int p = 1, k = 0; (i & (p-1)) == (p-1); p <<= 1, k++) {
				int lo1 = p;
				int lo2 = i + 1 - p;
				int s = 2*p - 1;
				auto fb = std::span<const T>(f).subspan(p, p);
				auto gb = std::span<const T>(g).subspan(p, p);
				auto out = std::span<T>(res).subspan(lo1 + lo2, s);
				if (i == 2*p-1) {
					f_blocks.emplace_back();
					g_blocks.emplace_back();
					fft::multiply<E>(fb, f_blocks[k], gb, g_blocks[k], out, fft::add_op{});
					break;
				}
				// both products keep f on the left: f_hi * g_lo + f_lo * g_hi
				fft::transformed<E> cf, cg;
				fft::multiply_add2<E>(
						fb, f_blocks[k], std::span<const T>(g).subspan(lo2, p), cg,
						std::span<const T>(f).subspan(lo2, p), cf, gb, g_blocks[k],
						out, fft::add_op{});
			}
		}
		i++;
	}

	T back() {
		return res[i-1];
	}
};

template <fft::engine E> struct online_squarer {
	using T = typename E::value_type;
	int N; int i;
	std::vector<T> f;
	std::vector<T> res;
	std::vector<fft::transformed<E>> f_blocks;

	online_squarer(int N_) : N(N_), i(0), f(N, T{}), res(2*N+1, T{}) {}

	T peek() {
		return res[i];
	}

	void push(T v_f) {
		assert(i < N);
		f[i] = v_f;
		if (i == 0) {
			res[0] += v_f * v_f;
		} else {
			if constexpr (E::commutative) res[i] += (v_f + v_f) * f[0];
			else res[i] += v_f * f[0] + f[0] * v_f;
			for (int p = 1, k = 0; (i & (p-1)) == (p-1); p <<= 1, k++) {
				int lo1 = p;
				int lo2 = i + 1 - p;
				int s = 2*p - 1;
				auto fb = std::span<const T>(f).subspan(p, p);
				auto fw = std::span<const T>(f).subspan(lo2, p);
				auto out = std::span<T>(res).subspan(lo1 + lo2, s);
				if (i == 2*p-1) {
					f_blocks.emplace_back();
					fft::square<E>(fb, f_blocks[k], out, fft::add_op{});
					break;
				}
				fft::transformed<E> cw;
				if constexpr (E::commutative) {
					fft::multiply<E>(fb, f_blocks[k], fw, cw, out, fft::add_twice_op{});
				} else {
					// f_hi * f_lo + f_lo * f_hi from the same two transforms
					fft::multiply_add2<E>(fb, f_blocks[k], fw, cw,
							fw, cw, fb, f_blocks[k], out, fft::add_op{});
				}
			}
		}
		i++;
	}

	T back() {
		return res[i-1];
	}
};

/* namespace ecnerwala */ }
#line 2 "src/fft/test_util.test.hpp"

#line 5 "src/fft/test_util.test.hpp"

#line 2 "src/fft/engines/real.hpp"

#line 8 "src/fft/engines/real.hpp"

#line 11 "src/fft/engines/real.hpp"

namespace ecnerwala::fft::engines {

// Convolve real (floating point) values by packing into complex numbers with
//   a'[t] = a[2t] + i * a[2t+1]
// We use conjugate symmetry to untangle/retangle the two.
// TODO: Add type bounds?
template <typename dbl = double> struct real {
	using value_type = dbl;
	static constexpr bool commutative = true;
	using cnum = cplx<dbl>;
	using core = fft_core<cnum>;
	struct transformed {
		vector<cnum> v;
		int size() const { return 2 * sz(v); }
	};
	using product = transformed;
	// Precision is caller-managed for this engine (see add), so scale is untracked.
	static constexpr int unit_scale = 0;
	template <int A = 0> using transformed_t = transformed;
	template <int K = 0> using product_t = product;

	static int packed_size(int n) { return std::max(n / 2, 1); }
	static void pack(std::span<const dbl> a, std::span<cnum> c) {
		for (int i = 0; i < sz(a); i++) (i & 1 ? c[i/2].y : c[i/2].x) = a[i];
	}
	// Spectrum of the real (odd = false) or imaginary (odd = true) part of the packed
	// sequence at bitrev entry t, by conjugate symmetry with the entry of w^{-k}.
	static cnum part(const transformed& f, int t, bool odd) {
		cnum g = conj(f.v[core::conj_index(t)]);
		return odd ? (f.v[t] - g) * cnum(0, dbl(-0.5)) : (f.v[t] + g) * cnum(dbl(0.5));
	}
	// Given the spectra (s0, s1) of a real sequence x at w_{2mo}^q and w_{2mo}^{q+mo},
	// the packed-transform entry of x at packed size mo: the even/odd interleaves of x
	// have spectra (s0 +- s1)/2 (the odd one twisted by w_{2mo}^{-q}).
	static cnum retangle(cnum s0, cnum s1, int mo, int q) {
		cnum s = (s0 + s1) * cnum(dbl(0.5));
		cnum d = (s0 - s1) * cnum(dbl(0.5)) * core::inv_rt[mo + q];
		return s + cnum(-d.y, d.x);
	}

	static transformed transform(std::span<const dbl> a, int n) {
		assert(sz(a) <= 2 * n);
		transformed r;
		r.v.assign(packed_size(n), cnum(0));
		for (int i = 0; i < sz(a); i++) {
			int j = i < n ? i : i - n;
			((j & 1) ? r.v[j/2].y : r.v[j/2].x) += a[i];
		}
		core::forward(std::span<cnum>(r.v));
		return r;
	}
	static void extend_to(transformed& t, int m, std::span<const dbl> coeffs) {
		assert(!(m & (m-1)) && sz(coeffs) <= 2 * m);
		if (t.size() >= m) return;
		if (t.size() == 0) { t = transform(coeffs, m); return; }
		auto buf = buffer_pool<cnum>::get((sz(coeffs) + 1) / 2);
		std::fill(buf.span().begin(), buf.span().end(), cnum(0));
		pack(coeffs, buf.span());
		while (t.size() < m) {
			int s = sz(t.v);
			t.v.resize(2 * s);
			// packed coeffs past 2s are zero: they didn't fit in the transform we're a prefix of
			core::extend(
				std::span<cnum>(t.v),
				std::span<const cnum>(buf.span()).first(size_t(min(sz(buf.span()), 2 * s)))
			);
		}
	}
	static transformed downsample(const transformed& t, int n, bool odd) { return half(t, n, odd); }
	// A(-x) negates the odd (imaginary-slot) coefficients, i.e. conjugates the packed
	// sequence; the transform of a conjugated sequence is the conjugate at w^(-k).
	static transformed negate_arg(const transformed& t, int n) {
		int m = packed_size(n);
		assert(n >= 2 && sz(t.v) >= m);
		transformed r; r.v.resize(m);
		for (int j = 0; j < m; j++) r.v[j] = conj(t.v[core::conj_index(j)]);
		return r;
	}
	static transformed half(const transformed& f, int n, bool odd) {
		assert(n >= 2 && f.size() >= 2 * n);
		int mo = n / 2;
		core::init(2 * mo);
		transformed r; r.v.resize(mo);
		for (int u = 0; u < mo; u++) {
			r.v[u] = retangle(part(f, 2*u, odd), part(f, 2*u+1, odd), mo, core::brev(u, mo));
		}
		return r;
	}
	static product mul(const transformed& a, const transformed& b, int n) {
		int m = packed_size(n);
		assert(a.size() >= n && b.size() >= n);
		core::init(2 * m);
		product p; p.v.resize(m);
		for (int t = 0; t < m; t++) {
			int k = core::brev(t, m);
			cnum w = core::rt[m + k];
			cnum xa = part(a, t, false), ya = part(a, t, true);
			cnum xb = part(b, t, false), yb = part(b, t, true);
			// full spectra at w_{2m}^k and w_{2m}^{k+m} = -w_{2m}^k
			cnum p0 = (xa + w * ya) * (xb + w * yb);
			cnum p1 = (xa - w * ya) * (xb - w * yb);
			p.v[t] = retangle(p0, p1, m, k);
		}
		return p;
	}
	static product sq(const transformed& a, int n) { return mul(a, a, n); }
	static product mul2(
		const transformed& a1, const transformed& b1,
		const transformed& a2, const transformed& b2,
		int n
	) {
		int m = packed_size(n);
		assert(a1.size() >= n && b1.size() >= n && a2.size() >= n && b2.size() >= n);
		core::init(2 * m);
		product p; p.v.resize(m);
		for (int t = 0; t < m; t++) {
			int k = core::brev(t, m);
			cnum w = core::rt[m + k];
			cnum xa1 = part(a1, t, false), ya1 = part(a1, t, true);
			cnum xb1 = part(b1, t, false), yb1 = part(b1, t, true);
			cnum xa2 = part(a2, t, false), ya2 = part(a2, t, true);
			cnum xb2 = part(b2, t, false), yb2 = part(b2, t, true);
			cnum p0 = (xa1 + w * ya1) * (xb1 + w * yb1) + (xa2 + w * ya2) * (xb2 + w * yb2);
			cnum p1 = (xa1 - w * ya1) * (xb1 - w * yb1) + (xa2 - w * ya2) * (xb2 - w * yb2);
			p.v[t] = retangle(p0, p1, m, k);
		}
		return p;
	}
	static product add(product&& a, const product& b) {
		assert(a.size() == b.size());
		for (int i = 0; i < sz(a.v); i++) a.v[i] = a.v[i] + b.v[i];
		return std::move(a);
	}
	template <typename Op = assign_op> static void finish(product&& p, std::span<dbl> out, Op op = {}) {
		int m = sz(p.v);
		assert(sz(out) <= 2 * m);
		core::inverse(std::span<cnum>(p.v));
		dbl d = dbl(1) / dbl(m);
		for (int i = 0; i < sz(out); i++) op(out[i], (i & 1 ? p.v[i/2].y : p.v[i/2].x) * d);
	}
};

/* namespace ecnerwala::fft::engines */ }
#line 11 "src/fft/test_util.test.hpp"

// Shared helpers for the fft/ unit tests.

namespace ecnerwala {
namespace fft {

template <typename T> std::vector<T> multiply_slow(const std::vector<T>& a, const std::vector<T>& b) {
	if (a.empty() || b.empty()) return {};
	std::vector<T> res(a.size() + b.size() - 1);
	for (int i = 0; i < int(a.size()); i++) {
		for (int j = 0; j < int(b.size()); j++) {
			res[i+j] += a[i] * b[j];
		}
	}
	return res;
}

// Small values for doubles so products are exactly representable; full range otherwise.
template <typename T> T rnd_val(std::mt19937& mt) {
	if constexpr (std::is_floating_point_v<T>) return T(int(mt() % 1024));
	else return T(mt());
}
template <typename T> void fill_rnd(std::vector<T>& v, std::mt19937& mt) {
	for (T& x : v) x = rnd_val<T>(mt);
}
template <typename T> void check_eq(const std::vector<T>& got, const std::vector<T>& want) {
	REQUIRE(got.size() == want.size());
	for (int i = 0; i < int(got.size()); i++) {
		INFO("i = " << i);
		if constexpr (std::is_floating_point_v<T>) REQUIRE(llround(got[i]) == llround(want[i]));
		else REQUIRE(got[i] == want[i]);
	}
}

#define ALL_ENGINES \
		engines::ntt<modnum<998244353>>, engines::ntt<mod_goldilocks>, engines::real<double>, \
		engines::split<modnum<int(1e9)+7>>, engines::crt<modnum<int(1e9)+7>>
#define MOD_ENGINES \
		engines::ntt<modnum<998244353>>, engines::ntt<mod_goldilocks>, \
		engines::split<modnum<int(1e9)+7>>, engines::crt<modnum<int(1e9)+7>>

}} // namespace ecnerwala::fft
#line 14 "src/fft/engines/algebras.test.cpp"

namespace ecnerwala {
namespace fft {

using namespace std;

// engine concept sanity checks for the wrapping algebras
static_assert(engine<engines::matrix<engines::ntt<modnum<998244353>>, 2>>);
static_assert(engine<engines::trunc<engines::ntt<modnum<998244353>>, 3>>);
// tracked inner engines work when the accumulated scale fits the budget (N <= 2)
static_assert(engine<engines::matrix<engines::split<modnum<int(1e9)+7>>, 2>>);
static_assert(engine<engines::trunc<engines::crt<modnum<int(1e9)+7>>, 2>>);
// the stable variants keep tracked inner engines sound at any N
static_assert(engine<engines::matrix_stable<engines::split<modnum<int(1e9)+7>>, 3>>);
static_assert(engine<engines::trunc_stable<engines::crt<modnum<int(1e9)+7>>, 3>>);

template <typename E, bool online, int N>
void test_matrix_engine(mt19937& mt) {
	using M = typename E::value_type;
	using num = std::remove_reference_t<decltype(std::declval<M&>()[{0, 0}])>;
	auto rnd_mat = [&]() {
		M m;
		for (int r = 0; r < N; r++) for (int c = 0; c < N; c++) m[{r, c}] = rnd_val<num>(mt);
		return m;
	};
	for (int la : {1, 2, 3, 17, 33}) {
		for (int lb : {1, 2, 16, 17}) {
			vector<M> a(la), b(lb);
			for (M& m : a) m = rnd_mat();
			for (M& m : b) m = rnd_mat();
			INFO("la = " << la << ", lb = " << lb);
			REQUIRE(multiply<E>(a, b) == multiply_slow(a, b));
		}
	}
	// square of a matrix sequence must keep both cross orders
	int n = 33;
	vector<M> f(n);
	for (M& m : f) m = rnd_mat();
	auto slow = multiply_slow(f, f);
	slow.resize(2*n, M{});
	vector<M> got(2*n - 1);
	square<E>(span<const M>(f), span<M>(got));
	REQUIRE(got == vector<M>(slow.begin(), slow.begin() + 2*n - 1));
	if constexpr (online) {
		online_squarer<E> os(n);
		for (int i = 0; i < n; i++) {
			os.push(f[i]);
			REQUIRE(os.back() == slow[i]);
		}
	}
}

TEMPLATE_TEST_CASE("matrix engine", "[fft]",
		engines::ntt<modnum<998244353>>,
		engines::split<modnum<int(1e9)+7>>,
		engines::crt<modnum<int(1e9)+7>>) {
	using IE = TestType;
	// the tracked engines' scale budget admits N = 2 (entries are N-addend sums), and
	// the non-commutative online squarer accumulates two N-addend products per window
	// (scale 2N), exceeding it
	constexpr int N = IE::unit_scale == 0 ? 3 : 2;
	mt19937 mt(Catch::getSeed());
	test_matrix_engine<engines::matrix<IE, N>, IE::unit_scale == 0, N>(mt);
	// the stable variant works at any N (and its online squarer stays at scale 2)
	test_matrix_engine<engines::matrix_stable<IE, 3>, true, 3>(mt);
}

template <typename E, typename num, int N>
void test_trunc_series_engine(mt19937& mt) {
	using P = typename E::value_type;
	auto rnd_p = [&]() {
		P p;
		for (int i = 0; i < N; i++) p[i] = rnd_val<num>(mt);
		return p;
	};
	for (int la : {1, 2, 3, 17, 33}) {
		for (int lb : {1, 2, 16, 17}) {
			vector<P> a(la), b(lb);
			for (P& p : a) p = rnd_p();
			for (P& p : b) p = rnd_p();
			INFO("la = " << la << ", lb = " << lb);
			REQUIRE(multiply<E>(a, b) == multiply_slow(a, b));
		}
	}
}

TEMPLATE_TEST_CASE("trunc_series engine", "[fft]",
		engines::ntt<modnum<998244353>>,
		engines::split<modnum<int(1e9)+7>>,
		engines::crt<modnum<int(1e9)+7>>) {
	using IE = TestType;
	using num = typename IE::value_type;
	constexpr int N = IE::unit_scale == 0 ? 3 : 2;
	mt19937 mt(Catch::getSeed());
	test_trunc_series_engine<engines::trunc<IE, N>, num, N>(mt);
	test_trunc_series_engine<engines::trunc_stable<IE, 3>, num, 3>(mt);
}

}} // namespace ecnerwala::fft
// clang-format off
// @formatter:off
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
#pragma GCC diagnostic ignored "-Wmisleading-indentation"
#pragma GCC diagnostic ignored "-Wmultistatement-macros"
#include <bits/stdc++.h>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_get_random_seed.hpp>
// src/fft/common.hpp
namespace ecnerwala{
template<class T>int sz(T&&arg){using std::size;return int(size(std::forward<T>(arg)));}
inline int nextPow2(int s){return 1<<(s>1?32-__builtin_clz(s-1):0);}
namespace fft{
using std::swap;
using std::vector;
using std::min;
using std::max;
#ifndef ECNERWALA_FFT_POOL_STORAGE
#define ECNERWALA_FFT_POOL_STORAGE
#endif
template<typename T>struct buffer_pool{
static inline ECNERWALA_FFT_POOL_STORAGE std::vector<std::vector<T>>free_list;
struct handle{
std::vector<T>v;
explicit handle(int n){
if(!free_list.empty()){
v=std::move(free_list.back());
free_list.pop_back();
}
v.assign(n,T());
}
handle(const handle&)=delete;
handle&operator=(const handle&)=delete;
handle(handle&&o)noexcept:v(std::move(o.v)){}
~handle(){if(v.capacity())free_list.push_back(std::move(v));}
T&operator[](int i){return v[i];}
operator std::span<T>(){return std::span<T>(v);}
std::span<T>span(){return std::span<T>(v);}
};
static handle get(int n){return handle(n);}
};
}
}
// src/fft/engine.hpp
namespace ecnerwala::fft{
struct assign_op{template<typename T>void operator()(T&d,T v)const{d=v;}};
struct add_op{template<typename T>void operator()(T&d,T v)const{d+=v;}};
struct sub_op{template<typename T>void operator()(T&d,T v)const{d-=v;}};
struct add_twice_op{template<typename T>void operator()(T&d,T v)const{d+=v+v;}};
template<typename E>
concept engine=requires(
std::span<const typename E::value_type>in,
std::span<typename E::value_type>out,
typename E::transformed&t,
const typename E::transformed&ct,
typename E::product&p,
const typename E::product&cp,
int n
){
typename E::value_type;
{E::transform(in,n)}->std::same_as<typename E::transformed>;
{ct.size()}->std::same_as<int>;
E::extend_to(t,n,in);
{E::downsample(ct,n,false)}->std::same_as<typename E::transformed>;
{E::downsample(cp,n,false)}->std::same_as<typename E::product>;
{E::negate_arg(ct,n)}->std::same_as<typename E::transformed>;
{E::mul(ct,ct,n)}->std::same_as<typename E::product>;
{E::sq(ct,n)}->std::same_as<typename E::product>;
{E::mul2(ct,ct,ct,ct,n)}->std::same_as<typename E::template product_t<2*E::unit_scale>>;
E::finish(std::move(p),out);
E::finish(std::move(p),out,add_op{});
E::finish(E::add(std::move(p),std::move(p)),out);
{E::add(E::transform(in,n),ct)}->std::same_as<typename E::template transformed_t<2*E::unit_scale>>;
{E::add(std::move(p),std::move(p))}->std::same_as<typename E::template product_t<2*E::unit_scale>>;
requires std::same_as<std::remove_cvref_t<decltype(E::commutative)>,bool>;
requires std::same_as<std::remove_cvref_t<decltype(E::unit_scale)>,int>;
};
template<typename A,typename B>
concept same_engine=std::same_as<typename A::engine_t,typename B::engine_t>;
template<engine E>using transformed=typename E::transformed;
}
// src/fft/engines/algebras.hpp
namespace ecnerwala::fft::engines{
template<typename num,int N>struct mat{
std::array<num,size_t(N)*N>a{};
num&operator[](std::array<int,2>rc){return a[size_t(rc[0])*N+rc[1]];}
const num&operator[](std::array<int,2>rc)const{return a[size_t(rc[0])*N+rc[1]];}
num*data(){return a.data();}
const num*data()const{return a.data();}
mat&operator+=(const mat&o){for(int i=0;i<N*N;i++)a[i]+=o.a[i];return*this;}
friend mat operator+(mat x,const mat&y){x+=y;return x;}
mat&operator-=(const mat&o){for(int i=0;i<N*N;i++)a[i]-=o.a[i];return*this;}
friend mat operator-(mat x,const mat&y){x-=y;return x;}
friend mat operator*(const mat&x,const mat&y){
mat r;
for(int i=0;i<N;i++)for(int k=0;k<N;k++)for(int j=0;j<N;j++)
r[{i,j}]+=x[{i,k}]*y[{k,j}];
return r;
}
mat&operator*=(const mat&o){return*this=*this*o;}
friend bool operator==(const mat&,const mat&)=default;
};
template<typename num,int N>struct trunc_series{
std::array<num,size_t(N)>a{};
num&operator[](int i){return a[size_t(i)];}
const num&operator[](int i)const{return a[size_t(i)];}
num*data(){return a.data();}
const num*data()const{return a.data();}
trunc_series&operator+=(const trunc_series&o){for(int i=0;i<N;i++)a[i]+=o.a[i];return*this;}
friend trunc_series operator+(trunc_series x,const trunc_series&y){x+=y;return x;}
trunc_series&operator-=(const trunc_series&o){for(int i=0;i<N;i++)a[i]-=o.a[i];return*this;}
friend trunc_series operator-(trunc_series x,const trunc_series&y){x-=y;return x;}
friend trunc_series operator*(const trunc_series&x,const trunc_series&y){
trunc_series r;
for(int i=0;i<N;i++)for(int j=0;j<N-i;j++)r[i+j]+=x[i]*y[j];
return r;
}
trunc_series&operator*=(const trunc_series&o){return*this=*this*o;}
friend bool operator==(const trunc_series&,const trunc_series&)=default;
};
template<int L>constexpr std::array<int,size_t(L)+1>componentwise_iota=[]{
std::array<int,size_t(L)+1>r{};
for(int i=0;i<=L;i++)r[size_t(i)]=i;
return r;
}();
template<engine E,typename V,int L,std::array<int,size_t(L)+1>Ofs=componentwise_iota<L>>
struct componentwise{
using S=typename E::value_type;
using value_type=V;
static constexpr int P=Ofs[size_t(L)];
static constexpr int unit_scale=E::unit_scale;
template<int A=unit_scale>struct transformed_t{
std::array<typename E::template transformed_t<A>,size_t(L)>t;
int size()const{return t[0].size();}
transformed_t()=default;
template<int A2>requires(A2!=A)explicit(A2>A)transformed_t(transformed_t<A2>&&o){
for(int c=0;c<L;c++)
t[c]=typename E::template transformed_t<A>(std::move(o.t[c]));
}
};
using transformed=transformed_t<>;
template<int K>struct product_t{
std::array<typename E::template product_t<K>,size_t(P)>t;
int size()const{return t[0].size();}
product_t()=default;
template<int K2>requires(K2!=K)explicit(K2>K)product_t(product_t<K2>&&o){
for(int c=0;c<P;c++)
t[c]=typename E::template product_t<K>(std::move(o.t[c]));
}
};
static transformed transform(std::span<const V>a,int n){
transformed r;
auto buf=buffer_pool<S>::get(sz(a));
for(int c=0;c<L;c++){
for(int i=0;i<sz(a);i++)buf[i]=a[i].data()[c];
r.t[c]=E::transform(std::span<const S>(buf.span()),n);
}
return r;
}
static void extend_to(transformed&t,int m,std::span<const V>coeffs){
if(t.size()>=m)return;
auto buf=buffer_pool<S>::get(sz(coeffs));
for(int c=0;c<L;c++){
for(int i=0;i<sz(coeffs);i++)buf[i]=coeffs[i].data()[c];
E::extend_to(t.t[c],m,std::span<const S>(buf.span()));
}
}
template<int A>static transformed_t<A>downsample(const transformed_t<A>&t,int n,bool odd){
transformed_t<A>r;
for(int c=0;c<L;c++)r.t[c]=E::downsample(t.t[c],n,odd);
return r;
}
template<int K>static product_t<K>downsample(const product_t<K>&p,int n,bool odd){
product_t<K>r;
for(int c=0;c<P;c++)r.t[c]=E::downsample(p.t[c],n,odd);
return r;
}
template<int A>static transformed_t<A>negate_arg(const transformed_t<A>&t,int n){
transformed_t<A>r;
for(int c=0;c<L;c++)r.t[c]=E::negate_arg(t.t[c],n);
return r;
}
template<int A,int B>static transformed_t<A+B>add(transformed_t<A>&&a,const transformed_t<B>&b){
transformed_t<A+B>r;
for(int c=0;c<L;c++)r.t[c]=E::add(std::move(a.t[c]),b.t[c]);
return r;
}
template<int K1,int K2>static product_t<K1+K2>add(product_t<K1>&&a,product_t<K2>&&b){
product_t<K1+K2>r;
for(int c=0;c<P;c++)r.t[c]=E::add(std::move(a.t[c]),std::move(b.t[c]));
return r;
}
template<int K,typename Op=assign_op>static void finish(product_t<K>&&p,std::span<V>out,Op op={}){
auto buf=buffer_pool<S>::get(sz(out));
auto emit=[&](std::span<V>dst){
for(int c=0;c<L;c++){
E::finish(std::move(p.t[Ofs[size_t(c)]]),buf.span());
for(int j=Ofs[size_t(c)]+1;j<Ofs[size_t(c)+1];j++)
E::finish(std::move(p.t[j]),buf.span(),add_op{});
for(int i=0;i<sz(dst);i++)dst[i].data()[c]=buf[i];
}
};
if constexpr(std::same_as<Op,assign_op>){
emit(out);
}else{
auto vbuf=buffer_pool<V>::get(sz(out));
emit(vbuf.span());
for(int i=0;i<sz(out);i++)op(out[i],vbuf.span()[i]);
}
}
};
template<engine E,int N>
struct matrix:componentwise<E,mat<typename E::value_type,N>,N*N>{
using base=componentwise<E,mat<typename E::value_type,N>,N*N>;
static constexpr bool commutative=false;
static constexpr int unit_scale=base::unit_scale;
template<int A=unit_scale>using transformed_t=typename base::template transformed_t<A>;
template<int K>using product_t=typename base::template product_t<K*N>;
using transformed=typename base::transformed;
using product=product_t<unit_scale*unit_scale>;
template<int A,int B,int k=0>
static auto entry(const transformed_t<A>&a,const transformed_t<B>&b,int r,int c,int n){
auto e=E::mul(a.t[size_t(r)*N+k],b.t[size_t(k)*N+c],n);
if constexpr(k+1==N)return e;
else return E::add(std::move(e),entry<A,B,k+1>(a,b,r,c,n));
}
template<int A,int B>
static product_t<A*B>mul(const transformed_t<A>&a,const transformed_t<B>&b,int n){
product_t<A*B>p;
for(int r=0;r<N;r++)for(int c=0;c<N;c++)
p.t[size_t(r)*N+c]=entry<A,B>(a,b,r,c,n);
return p;
}
template<int A>static auto sq(const transformed_t<A>&a,int n){return mul(a,a,n);}
template<int A1,int B1,int A2,int B2,int k=0>
static auto entry2(
const transformed_t<A1>&a1,const transformed_t<B1>&b1,
const transformed_t<A2>&a2,const transformed_t<B2>&b2,
int r,int c,int n
){
auto e=E::mul2(
a1.t[size_t(r)*N+k],b1.t[size_t(k)*N+c],
a2.t[size_t(r)*N+k],b2.t[size_t(k)*N+c],
n
);
if constexpr(k+1==N)return e;
else return E::add(std::move(e),entry2<A1,B1,A2,B2,k+1>(a1,b1,a2,b2,r,c,n));
}
template<int A1,int B1,int A2,int B2>
static product_t<A1*B1+A2*B2>mul2(
const transformed_t<A1>&a1,const transformed_t<B1>&b1,
const transformed_t<A2>&a2,const transformed_t<B2>&b2,
int n
){
product_t<A1*B1+A2*B2>p;
for(int r=0;r<N;r++)for(int c=0;c<N;c++)
p.t[size_t(r)*N+c]=entry2<A1,B1,A2,B2>(a1,b1,a2,b2,r,c,n);
return p;
}
};
template<engine E,int N>
struct trunc:componentwise<E,trunc_series<typename E::value_type,N>,N>{
using base=componentwise<E,trunc_series<typename E::value_type,N>,N>;
static constexpr bool commutative=E::commutative;
static constexpr int unit_scale=base::unit_scale;
template<int A=unit_scale>using transformed_t=typename base::template transformed_t<A>;
template<int K>using product_t=typename base::template product_t<K*N>;
using transformed=typename base::transformed;
using product=product_t<unit_scale*unit_scale>;
template<int A,int B,int s,int i=0>
static auto entry(const transformed_t<A>&a,const transformed_t<B>&b,int n){
auto e=E::mul(a.t[size_t(i)],b.t[size_t(s-i)],n);
if constexpr(i==s)return e;
else return E::add(std::move(e),entry<A,B,s,i+1>(a,b,n));
}
template<int A,int B>
static product_t<A*B>mul(const transformed_t<A>&a,const transformed_t<B>&b,int n){
product_t<A*B>p;
[&]<size_t...s_>(std::index_sequence<s_...>){
((p.t[s_]=entry<A,B,int(s_)>(a,b,n)),...);
}(std::make_index_sequence<size_t(N)>{});
return p;
}
template<int A>static auto sq(const transformed_t<A>&a,int n){return mul(a,a,n);}
template<int A1,int B1,int A2,int B2,int s,int i=0>
static auto entry2(
const transformed_t<A1>&a1,const transformed_t<B1>&b1,
const transformed_t<A2>&a2,const transformed_t<B2>&b2,
int n
){
auto e=E::mul2(a1.t[size_t(i)],b1.t[size_t(s-i)],a2.t[size_t(i)],b2.t[size_t(s-i)],n);
if constexpr(i==s)return e;
else return E::add(std::move(e),entry2<A1,B1,A2,B2,s,i+1>(a1,b1,a2,b2,n));
}
template<int A1,int B1,int A2,int B2>
static product_t<A1*B1+A2*B2>mul2(
const transformed_t<A1>&a1,const transformed_t<B1>&b1,
const transformed_t<A2>&a2,const transformed_t<B2>&b2,
int n
){
product_t<A1*B1+A2*B2>p;
[&]<size_t...s_>(std::index_sequence<s_...>){
((p.t[s_]=entry2<A1,B1,A2,B2,int(s_)>(a1,b1,a2,b2,n)),...);
}(std::make_index_sequence<size_t(N)>{});
return p;
}
};
template<int N>constexpr std::array<int,size_t(N)*N+1>matrix_stable_ofs=[]{
std::array<int,size_t(N)*N+1>r{};
for(int i=0;i<=N*N;i++)r[size_t(i)]=i*N;
return r;
}();
template<engine E,int N>
struct matrix_stable
:componentwise<E,mat<typename E::value_type,N>,N*N,matrix_stable_ofs<N>>{
using base=componentwise<E,mat<typename E::value_type,N>,N*N,matrix_stable_ofs<N>>;
static constexpr bool commutative=false;
static constexpr int unit_scale=base::unit_scale;
template<int A=unit_scale>using transformed_t=typename base::template transformed_t<A>;
template<int K>using product_t=typename base::template product_t<K>;
using transformed=typename base::transformed;
using product=product_t<unit_scale*unit_scale>;
template<int A,int B>
static product_t<A*B>mul(const transformed_t<A>&a,const transformed_t<B>&b,int n){
product_t<A*B>p;
for(int r=0;r<N;r++)for(int c=0;c<N;c++)for(int k=0;k<N;k++)
p.t[(size_t(r)*N+c)*N+k]=E::mul(a.t[size_t(r)*N+k],b.t[size_t(k)*N+c],n);
return p;
}
template<int A>static auto sq(const transformed_t<A>&a,int n){return mul(a,a,n);}
template<int A1,int B1,int A2,int B2>
static product_t<A1*B1+A2*B2>mul2(
const transformed_t<A1>&a1,const transformed_t<B1>&b1,
const transformed_t<A2>&a2,const transformed_t<B2>&b2,
int n
){
product_t<A1*B1+A2*B2>p;
for(int r=0;r<N;r++)for(int c=0;c<N;c++)for(int k=0;k<N;k++)
p.t[(size_t(r)*N+c)*N+k]=E::mul2(
a1.t[size_t(r)*N+k],b1.t[size_t(k)*N+c],
a2.t[size_t(r)*N+k],b2.t[size_t(k)*N+c],
n
);
return p;
}
};
template<int N>constexpr std::array<int,size_t(N)+1>trunc_series_stable_ofs=[]{
std::array<int,size_t(N)+1>r{};
for(int i=0;i<=N;i++)r[size_t(i)]=i*(i+1)/2;
return r;
}();
template<engine E,int N>
struct trunc_stable
:componentwise<E,trunc_series<typename E::value_type,N>,N,trunc_series_stable_ofs<N>>{
using base=componentwise<E,trunc_series<typename E::value_type,N>,N,trunc_series_stable_ofs<N>>;
static constexpr bool commutative=E::commutative;
static constexpr int unit_scale=base::unit_scale;
template<int A=unit_scale>using transformed_t=typename base::template transformed_t<A>;
template<int K>using product_t=typename base::template product_t<K>;
using transformed=typename base::transformed;
using product=product_t<unit_scale*unit_scale>;
template<int A,int B>
static product_t<A*B>mul(const transformed_t<A>&a,const transformed_t<B>&b,int n){
product_t<A*B>p;
for(int s=0;s<N;s++)for(int i=0;i<=s;i++)
p.t[size_t(trunc_series_stable_ofs<N>[size_t(s)]+i)]=E::mul(a.t[size_t(i)],b.t[size_t(s-i)],n);
return p;
}
template<int A>static auto sq(const transformed_t<A>&a,int n){return mul(a,a,n);}
template<int A1,int B1,int A2,int B2>
static product_t<A1*B1+A2*B2>mul2(
const transformed_t<A1>&a1,const transformed_t<B1>&b1,
const transformed_t<A2>&a2,const transformed_t<B2>&b2,
int n
){
product_t<A1*B1+A2*B2>p;
for(int s=0;s<N;s++)for(int i=0;i<=s;i++)
p.t[size_t(trunc_series_stable_ofs<N>[size_t(s)]+i)]=E::mul2(
a1.t[size_t(i)],b1.t[size_t(s-i)],
a2.t[size_t(i)],b2.t[size_t(s-i)],
n
);
return p;
}
};
}
// src/modnum.hpp
template<typename T>T mod_inv_in_range(T a,T m){
T x=a,y=m;
T vx=1,vy=0;
bool swap=false;
while(x){
T k=y/x;
y%=x;
vy+=k*vx;
std::swap(x,y);
std::swap(vx,vy);
swap^=1;
}
assert(y==1);
return swap?vy:m-vy;
}
template<typename T>struct extended_gcd_result{
T gcd;
T coeff_a,coeff_b;
};
template<typename T>extended_gcd_result<T>extended_gcd(T a,T b){
T x=a,y=b;
T ax=1,ay=0;
T bx=0,by=1;
while(x){
T k=y/x;
y%=x;
ay-=k*ax;
by-=k*bx;
std::swap(x,y);
std::swap(ax,ay);
std::swap(bx,by);
}
return{y,ay,by};
}
template<typename T>T mod_inv(T a,T m){
a%=m;
a=a<0?a+m:a;
return mod_inv_in_range(a,m);
}
template<typename Self>
struct num_ops{
Self operator+()const{return static_cast<const Self&>(*this);}
Self operator-()const{return static_cast<const Self&>(*this).neg();}
friend Self operator++(Self&a,int){Self r=a;++a;return r;}
friend Self operator--(Self&a,int){Self r=a;--a;return r;}
friend Self operator+(const Self&a,const Self&b){return Self(a)+=b;}
friend Self operator-(const Self&a,const Self&b){return Self(a)-=b;}
friend Self operator*(const Self&a,const Self&b){return Self(a)*=b;}
friend Self operator/(const Self&a,const Self&b){return Self(a)/=b;}
friend bool operator!=(const Self&a,const Self&b){return!(a==b);}
friend Self neg(const Self&a){return a.neg();}
friend Self inv(const Self&a){return a.inv();}
};
template<typename Self,typename V>
struct mod_ops:num_ops<Self>{
static_assert(std::unsigned_integral<V>);
V v;
struct is_reduced_tag{};
mod_ops():v(0){}
mod_ops(V v_,is_reduced_tag):v(v_){assert(v<Self::MOD);}
template<std::integral I>mod_ops(I x):v(Self::reduce(x)){}
static Self from_reduced(V v){return Self(v,is_reduced_tag{});}
static V reduce(std::signed_integral auto x){
using U=std::make_unsigned_t<decltype(x)>;
return x<0?V(Self::MOD-1-Self::reduce(U(~x))):Self::reduce(U(x));
}
explicit operator V()const{return v;}
std::make_signed_t<V>balanced()const{
return std::make_signed_t<V>(Self::MOD-v>v?v:v-Self::MOD);
}
friend bool operator==(const Self&a,const Self&b){return a.v==b.v;}
friend std::ostream&operator<<(std::ostream&out,const Self&n){return out<<n.v;}
friend std::istream&operator>>(std::istream&in,Self&n){int64_t v_;in>>v_;n=Self(v_);return in;}
Self&operator++(){
++v;
if(v==Self::MOD)v=0;
return self();
}
Self&operator--(){
if(v==0)v=Self::MOD;
--v;
return self();
}
Self&operator+=(const Self&o){v=Self::sub_mod_raw(v,Self::MOD-o.v);return self();}
Self&operator-=(const Self&o){v=Self::sub_mod_raw(v,o.v);return self();}
Self&operator/=(const Self&o){return self()*=o.inv();}
static V sub_mod_raw(V a,V b){return a<b?a-b+Self::MOD:a-b;}
Self neg()const{return from_reduced(v?Self::MOD-v:0);}
Self inv()const{return from_reduced(mod_inv_in_range(v,Self::MOD));}
private:
Self&self(){return static_cast<Self&>(*this);}
};
template<auto MOD_>struct modnum:mod_ops<modnum<MOD_>,std::make_unsigned_t<decltype(MOD_)>>{
using Self=modnum;
static_assert(MOD_>0,"MOD must be positive");
using V=std::make_unsigned_t<decltype(MOD_)>;
static constexpr V MOD=V(MOD_);
using base=mod_ops<modnum,V>;
using base::base;
using base::v;
using base::reduce;
static V reduce(std::unsigned_integral auto x){return V(x%MOD);}
explicit operator std::make_signed_t<V>()const
requires(MOD<=V(std::numeric_limits<std::make_signed_t<V>>::max()))
{
return std::make_signed_t<V>(v);
}
Self&operator*=(const Self&o){
if constexpr(sizeof(V)<=4)v=V(uint64_t(v)*o.v%MOD);
else v=V(__uint128_t(v)*o.v%MOD);
return*this;
}
};
struct mod_goldilocks:mod_ops<mod_goldilocks,uint64_t>{
using Self=mod_goldilocks;
static constexpr uint64_t MOD=0xffffffff00000001ull;
static constexpr uint64_t EPS=-MOD;
static constexpr uint64_t PRIMITIVE_ROOT=2717;
using base=mod_ops<mod_goldilocks,uint64_t>;
using base::base;
using base::reduce;
mod_goldilocks()=default;
mod_goldilocks(__int128_t a):base(a<0?uint64_t(MOD-1-__uint128_t(~a)%MOD):uint64_t(__uint128_t(a)%MOD),is_reduced_tag{}){}
mod_goldilocks(__uint128_t a):base(uint64_t(a%MOD),is_reduced_tag{}){}
static uint64_t reduce(std::unsigned_integral auto x){
static_assert(sizeof(x)<=8);
uint64_t a=x;
return a>=MOD?a-MOD:a;
}
static uint64_t sub_mod_raw(uint64_t a,uint64_t b){
#if defined(__x86_64__)
uint64_t res_wrapped=a;
uint64_t adjustment=b;
asm(
"sub %[y], %[x]\n\t"
"sbb %k[y], %k[y]\n\t"
:[x]"+r"(res_wrapped),
[y]"+r"(adjustment)
:
:"cc"
);
#else
uint64_t res_wrapped=a-b;
uint64_t adjustment=(res_wrapped>a)?EPS:0;
#endif
return res_wrapped-adjustment;
}
static uint64_t reduce_u160_raw(uint64_t lo,uint32_t mi,uint64_t hi){
return sub_mod_raw(sub_mod_raw(lo,hi),MOD-(uint64_t(mi)<<32)+mi);
}
static uint64_t reduce_u128_raw(__uint128_t v){
uint64_t hi=uint64_t(v>>64);
uint64_t lo=uint64_t(v);
uint32_t hi_hi=uint32_t(hi>>32);
uint32_t hi_lo=uint32_t(hi);
return reduce_u160_raw(lo,hi_lo,hi_hi);
}
Self&operator*=(Self o){
v=reduce_u128_raw(__uint128_t(v)*__uint128_t(o.v));
return*this;
}
};
template<typename T>T power(T a,long long b){
assert(b>=0);
T r=1;while(b){if(b&1)r*=a;b>>=1;a*=a;}return r;
}
template<typename U,typename V>struct pairnum:num_ops<pairnum<U,V>>{
using Self=pairnum;
U u;
V v;
pairnum():u(0),v(0){}
pairnum(long long val):u(val),v(val){}
pairnum(const U&u_,const V&v_):u(u_),v(v_){}
friend std::ostream&operator<<(std::ostream&out,const Self&n){return out<<'('<<n.u<<','<<' '<<n.v<<')';}
friend std::istream&operator>>(std::istream&in,Self&n){long long val;in>>val;n=Self(val);return in;}
friend bool operator==(const Self&a,const Self&b){return a.u==b.u&&a.v==b.v;}
Self inv()const{
return Self(u.inv(),v.inv());
}
Self neg()const{
return Self(u.neg(),v.neg());
}
Self&operator++(){
++u,++v;
return*this;
}
Self&operator--(){
--u,--v;
return*this;
}
Self&operator+=(const Self&o){
u+=o.u;
v+=o.v;
return*this;
}
Self&operator-=(const Self&o){
u-=o.u;
v-=o.v;
return*this;
}
Self&operator*=(const Self&o){
u*=o.u;
v*=o.v;
return*this;
}
Self&operator/=(const Self&o){
u/=o.u;
v/=o.v;
return*this;
}
};
template<typename tag>struct dynamic_modnum:mod_ops<dynamic_modnum<tag>,uint32_t>{
using Self=dynamic_modnum;
private:
inline static uint32_t MOD_=0;
inline static uint64_t BARRETT_M=0;
public:
static constexpr uint32_t const&MOD=MOD_;
using base=mod_ops<dynamic_modnum,uint32_t>;
using base::base;
using base::v;
using base::reduce;
static void set_mod(int mod){
assert(mod>0);
MOD_=uint32_t(mod);
BARRETT_M=(uint64_t(-1)/MOD);
}
static uint32_t barrett_reduce_partial(uint64_t a){
return uint32_t(a-uint64_t((__uint128_t(BARRETT_M)*a)>>64)*MOD);
}
static uint32_t barrett_reduce(uint64_t a){
int32_t res=int32_t(barrett_reduce_partial(a)-MOD);
return uint32_t((res<0)?res+int32_t(MOD):res);
}
struct mod_reader{
friend std::istream&operator>>(std::istream&i,mod_reader){
int mod;i>>mod;
Self::set_mod(mod);
return i;
}
};
static mod_reader MOD_READER(){
return mod_reader();
}
static uint32_t reduce(std::unsigned_integral auto x){
static_assert(sizeof(x)<=8);
return barrett_reduce(x);
}
explicit operator int()const{return int(v);}
Self&operator*=(const Self&o){
v=barrett_reduce(uint64_t(v)*o.v);
return*this;
}
};
template<typename T>struct mod_constraint{
T v,mod;
friend mod_constraint operator&(mod_constraint a,mod_constraint b){
if(a.mod<b.mod)std::swap(a,b);
if(b.mod==1)return a;
extended_gcd_result<T>egcd=extended_gcd<T>(a.mod,b.mod);
assert(a.v%egcd.gcd==b.v%egcd.gcd);
T extra=b.v-a.v%b.mod;
extra/=egcd.gcd;
extra*=egcd.coeff_a;
extra%=b.mod/egcd.gcd;
extra+=(extra<0)?b.mod/egcd.gcd:0;
return mod_constraint{
a.v+extra*a.mod,
a.mod*(b.mod/egcd.gcd)
};
}
};
// src/fft/core.hpp
namespace ecnerwala::fft{
template<typename dbl>struct cplx{
dbl x,y;
cplx(dbl x_=0,dbl y_=0):x(x_),y(y_){}
friend cplx operator+(cplx a,cplx b){return cplx(a.x+b.x,a.y+b.y);}
friend cplx operator-(cplx a,cplx b){return cplx(a.x-b.x,a.y-b.y);}
friend cplx operator*(cplx a,cplx b){return cplx(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
friend cplx conj(cplx a){return cplx(a.x,-a.y);}
friend cplx inv(cplx a){dbl n=(a.x*a.x+a.y*a.y);return cplx(a.x/n,-a.y/n);}
};
template<typename num>struct getRoot{
static num f(int k)=delete;
};
template<typename dbl>struct getRoot<cplx<dbl>>{
static cplx<dbl>f(int k){
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
dbl a=2*M_PI/k;
return cplx<dbl>(cos(a),sin(a));
}
};
template<int MOD>struct primitive_root{
static const int value;
};
template<>struct primitive_root<998244353>{
static const int value=3;
};
template<>struct primitive_root<(15<<27)+1>{
static const int value=31;
};
template<>struct primitive_root<(127<<24)+1>{
static const int value=3;
};
template<>struct primitive_root<(7<<26)+1>{
static const int value=3;
};
template<>struct primitive_root<(5<<25)+1>{
static const int value=3;
};
template<int MOD>struct getRoot<modnum<MOD>>{
static modnum<MOD>f(int k){
assert((MOD-1)%k==0);
return power(modnum<MOD>(primitive_root<MOD>::value),(MOD-1)/k);
}
};
template<>struct getRoot<mod_goldilocks>{
static mod_goldilocks f(int k){
assert((mod_goldilocks::MOD-1)%k==0);
return power(mod_goldilocks(mod_goldilocks::PRIMITIVE_ROOT),(mod_goldilocks::MOD-1)/k);
}
};
template<typename num>struct fft_core{
static inline vector<int>rev;
static inline vector<num>rt,inv_rt;
static void init(int n){
if(n<=sz(rt))return;
rev.resize(n);
for(int i=0;i<n;i++){
rev[i]=(rev[i>>1]|((i&1)*n))>>1;
}
rt.reserve(n);inv_rt.reserve(n);
while(sz(rt)<2&&sz(rt)<n){rt.push_back(num(1));inv_rt.push_back(num(1));}
for(int k=sz(rt);k<n;k*=2){
rt.resize(2*k);inv_rt.resize(2*k);
num z=getRoot<num>::f(2*k);
num iz=inv(z);
for(int i=k/2;i<k;i++){
rt[2*i]=rt[i],rt[2*i+1]=rt[i]*z;
inv_rt[2*i]=inv_rt[i],inv_rt[2*i+1]=inv_rt[i]*iz;
}
}
}
static int brev(int i,int n){
int s=__builtin_ctz(unsigned(sz(rev)/n));
return rev[i]>>s;
}
static int conj_index(int j){
return j==0?0:j^((1<<(31-__builtin_clz(unsigned(j))))-1);
}
static void forward(std::span<num>a){
int n=sz(a);
if(n<=1)return;
init(n);
for(int k=n/2;k>=1;k/=2){
for(int i=0;i<n;i+=2*k){
for(int j=0;j<k;j++){
num u=a[i+j],v=a[i+j+k];
a[i+j]=u+v;
a[i+j+k]=(u-v)*rt[j+k];
}
}
}
}
static void inverse(std::span<num>a){
int n=sz(a);
if(n<=1)return;
init(n);
for(int k=1;k<n;k*=2){
for(int i=0;i<n;i+=2*k){
for(int j=0;j<k;j++){
num t=inv_rt[j+k]*a[i+j+k];
a[i+j+k]=a[i+j]-t;
a[i+j]=a[i+j]+t;
}
}
}
}
static void extend(std::span<num>t,std::span<const num>coeffs){
int n=sz(t)/2;
assert(sz(coeffs)<=2*n);
init(sz(t));
auto b=t.subspan(n,n);
int lo=min(sz(coeffs),n);
for(int i=0;i<lo;i++){
b[i]=coeffs[i]*rt[n+i];
}
std::fill(b.begin()+lo,b.end(),num(0));
for(int i=n;i<sz(coeffs);i++){
b[i-n]=b[i-n]-coeffs[i]*rt[i];
}
forward(b);
}
static void even_half(std::span<const num>t,std::span<num>out){
int n=sz(out);
assert(sz(t)>=2*n);
num half=inv(num(2));
for(int j=0;j<n;j++)out[j]=(t[2*j]+t[2*j+1])*half;
}
static void odd_half(std::span<const num>t,std::span<num>out){
int n=sz(out);
assert(sz(t)>=2*n);
init(2*n);
num half=inv(num(2));
for(int j=0;j<n;j++){
out[j]=(t[2*j]-t[2*j+1])*half*inv_rt[n+brev(j,n)];
}
}
};
}
// src/fft/engines/ntt.hpp
namespace ecnerwala::fft::engines{
template<typename num>struct ntt{
using value_type=num;
static constexpr bool commutative=true;
using core=fft_core<num>;
struct transformed{
vector<num>v;
int size()const{return sz(v);}
};
using product=transformed;
static constexpr int unit_scale=0;
template<int A=0>using transformed_t=transformed;
template<int K=0>using product_t=product;
static transformed transform(std::span<const num>a,int n){
assert(sz(a)<=2*n);
transformed r;
r.v.assign(n,num(0));
int lo=min(sz(a),n);
std::copy(a.begin(),a.begin()+lo,r.v.begin());
for(int i=n;i<sz(a);i++)r.v[i-n]+=a[i];
core::forward(std::span<num>(r.v));
return r;
}
static void extend_to(transformed&t,int m,std::span<const num>coeffs){
assert(!(m&(m-1))&&sz(coeffs)<=2*m);
if(t.size()>=m)return;
if(t.size()==0){t=transform(coeffs,m);return;}
while(t.size()<m){
int s=t.size();
t.v.resize(2*s);
core::extend(std::span<num>(t.v),coeffs.first(size_t(min(sz(coeffs),2*s))));
}
}
static transformed downsample(const transformed&t,int n,bool odd){
transformed r;r.v.resize(n);
if(odd)core::odd_half(std::span<const num>(t.v),std::span<num>(r.v));
else core::even_half(std::span<const num>(t.v),std::span<num>(r.v));
return r;
}
static transformed negate_arg(const transformed&t,int n){
assert(n>=2&&t.size()>=n);
transformed r;r.v.resize(n);
for(int j=0;j<n;j++)r.v[j]=t.v[j^1];
return r;
}
static product mul(const transformed&a,const transformed&b,int n){
assert(a.size()>=n&&b.size()>=n);
product p;p.v.resize(n);
for(int i=0;i<n;i++)p.v[i]=a.v[i]*b.v[i];
return p;
}
static product sq(const transformed&a,int n){return mul(a,a,n);}
static product mul2(
const transformed&a1,const transformed&b1,
const transformed&a2,const transformed&b2,
int n
){
assert(a1.size()>=n&&b1.size()>=n&&a2.size()>=n&&b2.size()>=n);
product p;p.v.resize(n);
for(int i=0;i<n;i++)p.v[i]=a1.v[i]*b1.v[i]+a2.v[i]*b2.v[i];
return p;
}
static product add(product&&a,const product&b){
assert(a.size()==b.size());
for(int i=0;i<a.size();i++)a.v[i]+=b.v[i];
return std::move(a);
}
template<typename Op=assign_op>static void finish(product&&p,std::span<num>out,Op op={}){
int n=p.size();
assert(sz(out)<=n);
core::inverse(std::span<num>(p.v));
num d=inv(num(n));
for(int i=0;i<sz(out);i++)op(out[i],p.v[i]*d);
}
};
}
// src/fft/engines/split.hpp
namespace ecnerwala::fft::engines{
template<typename mnum>struct split{
static_assert(sizeof(decltype(mnum::MOD))<=4,"limbs must fit 15 bits");
using value_type=mnum;
static constexpr bool commutative=true;
static constexpr int unit_scale=1;
using cnum=cplx<double>;
using core=fft_core<cnum>;
template<int A=1>struct transformed_t{
vector<cnum>v;
int size()const{return sz(v);}
transformed_t()=default;
explicit transformed_t(vector<cnum>&&v_):v(std::move(v_)){}
template<int A2>requires(A2!=A)explicit(A2>A)transformed_t(transformed_t<A2>&&o)
:v(std::move(o.v)){}
};
using transformed=transformed_t<1>;
template<int K>struct product_t{
vector<cnum>lo,hi;
int size()const{return sz(lo);}
product_t()=default;
product_t(vector<cnum>&&lo_,vector<cnum>&&hi_):lo(std::move(lo_)),hi(std::move(hi_)){}
template<int K2>requires(K2!=K)explicit(K2>K)product_t(product_t<K2>&&o)
:lo(std::move(o.lo)),hi(std::move(o.hi)){}
};
using product=product_t<1>;
static cnum pack(mnum x){
int64_t v=x.balanced();
int64_t hi=(v+(1<<14))>>15;
return cnum(double(v-(hi<<15)),double(hi));
}
static transformed transform(std::span<const mnum>a,int n){
assert(sz(a)<=2*n);
transformed r;
r.v.assign(n,cnum(0));
for(int i=0;i<sz(a);i++){
int j=i<n?i:i-n;
r.v[j]=r.v[j]+pack(a[i]);
}
core::forward(std::span<cnum>(r.v));
return r;
}
static void extend_to(transformed&t,int m,std::span<const mnum>coeffs){
assert(!(m&(m-1))&&sz(coeffs)<=2*m);
if(t.size()>=m)return;
if(t.size()==0){t=transform(coeffs,m);return;}
auto buf=buffer_pool<cnum>::get(sz(coeffs));
for(int i=0;i<sz(coeffs);i++)buf[i]=pack(coeffs[i]);
while(t.size()<m){
int s=t.size();
t.v.resize(2*s);
core::extend(
std::span<cnum>(t.v),
std::span<const cnum>(buf.span()).first(size_t(min(sz(coeffs),2*s)))
);
}
}
static void downsample_core(std::span<const cnum>in,std::span<cnum>out,bool odd){
if(odd)core::odd_half(in,out);
else core::even_half(in,out);
}
template<int A>static transformed_t<A>downsample(const transformed_t<A>&t,int n,bool odd){
transformed_t<A>r;r.v.resize(n);
downsample_core(std::span<const cnum>(t.v),std::span<cnum>(r.v),odd);
return r;
}
template<int K>static product_t<K>downsample(const product_t<K>&p,int n,bool odd){
product_t<K>r;r.lo.resize(n);r.hi.resize(n);
downsample_core(std::span<const cnum>(p.lo),std::span<cnum>(r.lo),odd);
downsample_core(std::span<const cnum>(p.hi),std::span<cnum>(r.hi),odd);
return r;
}
template<int A>static transformed_t<A>negate_arg(const transformed_t<A>&t,int n){
assert(n>=2&&t.size()>=n);
transformed_t<A>r;r.v.resize(n);
for(int j=0;j<n;j++)r.v[j]=t.v[j^1];
return r;
}
template<int A,int B>static transformed_t<A+B>add(transformed_t<A>&&a,const transformed_t<B>&b){
transformed_t<A+B>r{std::move(a.v)};
add_into(r.v,b.v);
return r;
}
static void mul_impl(const vector<cnum>&a,const vector<cnum>&b,vector<cnum>&lo,vector<cnum>&hi,int n,bool acc=false){
core::init(n);
lo.resize(n);hi.resize(n);
for(int i=0;i<n;i++){
int ci=core::conj_index(i);
cnum g0=(b[i]+conj(b[ci]))*cnum(0.5);
cnum t=(b[i]-conj(b[ci]))*cnum(0.5);
cnum g1=cnum(t.y,-t.x);
if(acc){
lo[i]=lo[i]+a[i]*g0;
hi[i]=hi[i]+a[i]*g1;
}else{
lo[i]=a[i]*g0;
hi[i]=a[i]*g1;
}
}
}
template<int A,int B>static product_t<A*B>mul(const transformed_t<A>&a,const transformed_t<B>&b,int n){
assert(a.size()>=n&&b.size()>=n);
product_t<A*B>p;
mul_impl(a.v,b.v,p.lo,p.hi,n);
return p;
}
template<int A>static product_t<A*A>sq(const transformed_t<A>&a,int n){return mul(a,a,n);}
template<int A1,int B1,int A2,int B2>
static product_t<A1*B1+A2*B2>mul2(
const transformed_t<A1>&a1,const transformed_t<B1>&b1,
const transformed_t<A2>&a2,const transformed_t<B2>&b2,
int n
){
assert(a1.size()>=n&&b1.size()>=n&&a2.size()>=n&&b2.size()>=n);
product_t<A1*B1+A2*B2>p;
mul_impl(a1.v,b1.v,p.lo,p.hi,n);
mul_impl(a2.v,b2.v,p.lo,p.hi,n,true);
return p;
}
static void add_into(vector<cnum>&a,const vector<cnum>&b){
assert(sz(a)==sz(b));
for(int i=0;i<sz(a);i++)a[i]=a[i]+b[i];
}
template<int K1,int K2>static product_t<K1+K2>add(product_t<K1>&&a,product_t<K2>&&b){
product_t<K1+K2>r{std::move(a.lo),std::move(a.hi)};
add_into(r.lo,b.lo);
add_into(r.hi,b.hi);
return r;
}
template<int K=1,typename Op=assign_op>static void finish(product_t<K>&&p,std::span<mnum>out,Op op={}){
static_assert(K<=2,"split: accumulated scale too large");
int n=p.size();
assert(sz(out)<=n);
core::inverse(std::span<cnum>(p.lo));
core::inverse(std::span<cnum>(p.hi));
const int64_t m=mnum::MOD;
double d=1.0/double(n);
for(int i=0;i<sz(out);i++){
int64_t v=(llround(p.lo[i].x*d)
+(llround(p.lo[i].y*d)%m<<15)
+(llround(p.hi[i].x*d)%m<<15)
+(llround(p.hi[i].y*d)%m<<30))%m;
if(v<0)v+=m;
op(out[i],mnum(v));
}
}
};
}
// src/fft/engines/crt.hpp
namespace ecnerwala::fft::engines{
template<typename mnum,typename num1=mod_goldilocks,typename num2=modnum<(15<<27)+1>>
struct crt{
static_assert(sizeof(decltype(mnum::MOD))<=4,"n (MOD/2)^2 must fit the CRT modulus product");
using value_type=mnum;
static constexpr bool commutative=true;
static constexpr int unit_scale=1;
using E1=ntt<num1>;
using E2=ntt<num2>;
template<int A=1>struct transformed_t{
typename E1::transformed t1;
typename E2::transformed t2;
int size()const{return t1.size();}
transformed_t()=default;
transformed_t(typename E1::transformed&&t1_,typename E2::transformed&&t2_)
:t1(std::move(t1_)),t2(std::move(t2_)){}
template<int A2>requires(A2!=A)explicit(A2>A)transformed_t(transformed_t<A2>&&o)
:t1(std::move(o.t1)),t2(std::move(o.t2)){}
};
using transformed=transformed_t<1>;
template<int K>struct product_t{
typename E1::product p1;
typename E2::product p2;
int size()const{return sz(p1);}
product_t()=default;
product_t(typename E1::product&&p1_,typename E2::product&&p2_)
:p1(std::move(p1_)),p2(std::move(p2_)){}
template<int K2>requires(K2!=K)explicit(K2>K)product_t(product_t<K2>&&o)
:p1(std::move(o.p1)),p2(std::move(o.p2)){}
};
using product=product_t<1>;
static transformed transform(std::span<const mnum>a,int n){
assert(sz(a)<=2*n);
auto b1=buffer_pool<num1>::get(sz(a));
auto b2=buffer_pool<num2>::get(sz(a));
for(int i=0;i<sz(a);i++){int64_t v=a[i].balanced();b1[i]=num1(v);b2[i]=num2(v);}
return transformed{
E1::transform(std::span<const num1>(b1.span()),n),
E2::transform(std::span<const num2>(b2.span()),n),
};
}
static void extend_to(transformed&t,int m,std::span<const mnum>coeffs){
if(t.size()>=m)return;
auto b1=buffer_pool<num1>::get(sz(coeffs));
auto b2=buffer_pool<num2>::get(sz(coeffs));
for(int i=0;i<sz(coeffs);i++){int64_t v=coeffs[i].balanced();b1[i]=num1(v);b2[i]=num2(v);}
E1::extend_to(t.t1,m,std::span<const num1>(b1.span()));
E2::extend_to(t.t2,m,std::span<const num2>(b2.span()));
}
template<int A>static transformed_t<A>downsample(const transformed_t<A>&t,int n,bool odd){
return transformed_t<A>{E1::downsample(t.t1,n,odd),E2::downsample(t.t2,n,odd)};
}
template<int K>static product_t<K>downsample(const product_t<K>&p,int n,bool odd){
return product_t<K>{E1::downsample(p.p1,n,odd),E2::downsample(p.p2,n,odd)};
}
template<int A>static transformed_t<A>negate_arg(const transformed_t<A>&t,int n){
return transformed_t<A>{E1::negate_arg(t.t1,n),E2::negate_arg(t.t2,n)};
}
template<int A,int B>static transformed_t<A+B>add(transformed_t<A>&&a,const transformed_t<B>&b){
return transformed_t<A+B>{E1::add(std::move(a.t1),b.t1),E2::add(std::move(a.t2),b.t2)};
}
template<int A,int B>static product_t<A*B>mul(const transformed_t<A>&a,const transformed_t<B>&b,int n){
return product_t<A*B>{E1::mul(a.t1,b.t1,n),E2::mul(a.t2,b.t2,n)};
}
template<int A>static product_t<A*A>sq(const transformed_t<A>&a,int n){return mul(a,a,n);}
template<int A1,int B1,int A2,int B2>
static product_t<A1*B1+A2*B2>mul2(
const transformed_t<A1>&a1,const transformed_t<B1>&b1,
const transformed_t<A2>&a2,const transformed_t<B2>&b2,
int n
){
return product_t<A1*B1+A2*B2>{
E1::mul2(a1.t1,b1.t1,a2.t1,b2.t1,n),
E2::mul2(a1.t2,b1.t2,a2.t2,b2.t2,n),
};
}
template<int K1,int K2>static product_t<K1+K2>add(product_t<K1>&&a,product_t<K2>&&b){
return product_t<K1+K2>{E1::add(std::move(a.p1),b.p1),E2::add(std::move(a.p2),b.p2)};
}
template<int K=1,typename Op=assign_op>static void finish(product_t<K>&&p,std::span<mnum>out,Op op={}){
static_assert(K<=2,"crt: accumulated scale too large");
int n=p.size();
assert(sz(out)<=n);
auto o1=buffer_pool<num1>::get(sz(out));
auto o2=buffer_pool<num2>::get(sz(out));
E1::finish(std::move(p.p1),o1.span());
E2::finish(std::move(p.p2),o2.span());
num1 inv_n2=inv(num1(num2::MOD));
num2 inv_n1=inv(num2(num1::MOD));
__int128_t whole=__int128_t(num1::MOD)*__int128_t(num2::MOD);
mnum m1_mod=mnum(num1::MOD);
mnum m2_mod=mnum(num2::MOD);
mnum whole_mod=m1_mod*m2_mod;
for(int i=0;i<sz(out);i++){
num1 v1=o1[i]*inv_n2;
num2 v2=o2[i]*inv_n1;
mnum o_mod=mnum(uint64_t(v1))*m2_mod+mnum(int(v2))*m1_mod;
__int128_t o_exact=__int128_t(uint64_t(v1))*__int128_t(num2::MOD)+__int128_t(int(v2))*__int128_t(num1::MOD);
if(o_exact>=whole){o_exact-=whole;o_mod-=whole_mod;}
if(o_exact>whole/2)o_mod-=whole_mod;
op(out[i],o_mod);
}
}
};
}
// src/fft/multiply.hpp
namespace ecnerwala::fft{
template<engine E,typename Op=assign_op>
void multiply_circular(std::span<const typename E::value_type>a,std::span<const typename E::value_type>b,
std::span<typename E::value_type>out,int n,Op op={}){
assert(!(n&(n-1)));
auto ta=E::transform(a,n);
auto tb=E::transform(b,n);
E::finish(E::mul(ta,tb,n),out,op);
}
template<engine E,typename Op=assign_op>
void square_circular(std::span<const typename E::value_type>a,std::span<typename E::value_type>out,int n,Op op={}){
assert(!(n&(n-1)));
auto ta=E::transform(a,n);
E::finish(E::sq(ta,n),out,op);
}
namespace detail{
struct conv_size{int n;bool cut;};
inline conv_size conv_size_for(int s){
int n=nextPow2(s);
bool cut=(n==2*(s-1));
return{cut?n/2:n,cut};
}
template<typename T,typename Op>
void emit_linear(std::span<T>buf,int n,int s,bool cut,T c0,std::span<T>out,Op op){
T cn{};
if(cut){
cn=buf[0]-c0;
buf[0]=c0;
}
int lim=min(sz(out),min(s,n));
for(int i=0;i<lim;i++)op(out[i],buf[i]);
if(cut&&sz(out)>=s)op(out[s-1],cn);
}
template<typename T,typename Op>
struct cut_op{
Op op;
T*out0;
T c0;
T&cn;
void operator()(T&x,T v)const{
if(&x==out0){cn=v-c0;v=c0;}
op(x,v);
}
};
template<engine E,typename P,typename Op=assign_op>
void finish_linear(
P&&p,int n,int s,bool cut,
typename E::value_type c0,std::span<typename E::value_type>out,Op op={}
){
using T=typename E::value_type;
if(sz(out)==0)return;
int lim=min(sz(out),min(s,n));
if(!cut){
E::finish(std::move(p),out.subspan(0,lim),op);
}else{
T cn{};
E::finish(std::move(p),out.subspan(0,lim),cut_op<T,Op>{op,&out[0],c0,cn});
if(sz(out)>=s)op(out[s-1],cn);
}
}
}
template<engine E,typename Op=assign_op>
void multiply(std::span<const typename E::value_type>a,std::span<const typename E::value_type>b,
std::span<typename E::value_type>out,Op op={}){
using T=typename E::value_type;
if(sz(a)==0||sz(b)==0)return;
int s=sz(a)+sz(b)-1;
auto[n,cut]=detail::conv_size_for(s);
T c0=a[0]*b[0];
auto buf=buffer_pool<T>::get(n);
multiply_circular<E>(a,b,buf.span(),n);
detail::emit_linear<T>(buf.span(),n,s,cut,c0,out,op);
}
template<engine E,typename Op=assign_op>
void multiply(std::span<const typename E::value_type>a,transformed<E>&ta,
std::span<const typename E::value_type>b,transformed<E>&tb,
std::span<typename E::value_type>out,Op op={}){
using T=typename E::value_type;
if(sz(a)==0||sz(b)==0)return;
int s=sz(a)+sz(b)-1;
auto[n,cut]=detail::conv_size_for(s);
T c0=a[0]*b[0];
E::extend_to(ta,n,a);
E::extend_to(tb,n,b);
detail::finish_linear<E>(E::mul(ta,tb,n),n,s,cut,c0,out,op);
}
template<engine E,typename Op=assign_op>
void multiply_add2(std::span<const typename E::value_type>a1,transformed<E>&ta1,
std::span<const typename E::value_type>b1,transformed<E>&tb1,
std::span<const typename E::value_type>a2,transformed<E>&ta2,
std::span<const typename E::value_type>b2,transformed<E>&tb2,
std::span<typename E::value_type>out,Op op={}){
using T=typename E::value_type;
assert(sz(a1)>0&&sz(b1)>0&&sz(a2)>0&&sz(b2)>0);
int s=sz(a1)+sz(b1)-1;
assert(sz(a2)+sz(b2)-1==s);
auto[n,cut]=detail::conv_size_for(s);
T c0=a1[0]*b1[0]+a2[0]*b2[0];
E::extend_to(ta1,n,a1);E::extend_to(tb1,n,b1);
E::extend_to(ta2,n,a2);E::extend_to(tb2,n,b2);
detail::finish_linear<E>(E::mul2(ta1,tb1,ta2,tb2,n),n,s,cut,c0,out,op);
}
template<engine E>
void multiply_add2_cached(
std::span<const typename E::value_type>a1,transformed<E>&ta1,
std::span<const typename E::value_type>b1,transformed<E>&tb1,
std::span<const typename E::value_type>a2,transformed<E>&ta2,
std::span<const typename E::value_type>b2,transformed<E>&tb2,
std::vector<typename E::value_type>&coeffs,transformed<E>&t){
using T=typename E::value_type;
assert(sz(a1)>0&&sz(b1)>0&&sz(a2)>0&&sz(b2)>0);
int s=sz(a1)+sz(b1)-1;
assert(sz(a2)+sz(b2)-1==s);
coeffs.assign(size_t(s),T{});
t=transformed<E>{};
if constexpr(std::same_as<typename E::product,transformed<E>>){
auto[n,cut]=detail::conv_size_for(s);
T c0=a1[0]*b1[0]+a2[0]*b2[0];
E::extend_to(ta1,n,a1);E::extend_to(tb1,n,b1);
E::extend_to(ta2,n,a2);E::extend_to(tb2,n,b2);
auto p=E::mul2(ta1,tb1,ta2,tb2,n);
auto tp=p;
detail::finish_linear<E>(std::move(p),n,s,cut,c0,std::span<T>(coeffs));
t=std::move(tp);
}else{
multiply_add2<E>(a1,ta1,b1,tb1,a2,ta2,b2,tb2,std::span<T>(coeffs));
}
}
template<engine E>
void multiply_cached(std::span<const typename E::value_type>a,transformed<E>&ta,
std::span<const typename E::value_type>b,transformed<E>&tb,
std::vector<typename E::value_type>&coeffs,transformed<E>&t){
using T=typename E::value_type;
coeffs.assign(size_t(sz(a)&&sz(b)?sz(a)+sz(b)-1:0),T{});
t=transformed<E>{};
if(coeffs.empty())return;
int s=sz(coeffs);
if constexpr(std::same_as<typename E::product,transformed<E>>){
auto[n,cut]=detail::conv_size_for(s);
T c0=a[0]*b[0];
E::extend_to(ta,n,a);
E::extend_to(tb,n,b);
auto p=E::mul(ta,tb,n);
auto tp=p;
detail::finish_linear<E>(std::move(p),n,s,cut,c0,std::span<T>(coeffs));
t=std::move(tp);
}else{
multiply<E>(a,ta,b,tb,std::span<T>(coeffs));
}
}
template<engine E,typename Op=assign_op>
void square(std::span<const typename E::value_type>a,std::span<typename E::value_type>out,Op op={}){
using T=typename E::value_type;
if(sz(a)==0)return;
int s=2*sz(a)-1;
auto[n,cut]=detail::conv_size_for(s);
T c0=a[0]*a[0];
auto buf=buffer_pool<T>::get(n);
square_circular<E>(a,buf.span(),n);
detail::emit_linear<T>(buf.span(),n,s,cut,c0,out,op);
}
template<engine E,typename Op=assign_op>
void square(std::span<const typename E::value_type>a,transformed<E>&ta,
std::span<typename E::value_type>out,Op op={}){
using T=typename E::value_type;
if(sz(a)==0)return;
int s=2*sz(a)-1;
auto[n,cut]=detail::conv_size_for(s);
T c0=a[0]*a[0];
E::extend_to(ta,n,a);
detail::finish_linear<E>(E::sq(ta,n),n,s,cut,c0,out,op);
}
template<engine E>
void square_cached(std::span<const typename E::value_type>a,transformed<E>&ta,
std::vector<typename E::value_type>&coeffs,transformed<E>&t){
using T=typename E::value_type;
coeffs.assign(size_t(sz(a)?2*sz(a)-1:0),T{});
t=transformed<E>{};
if(coeffs.empty())return;
int s=sz(coeffs);
if constexpr(std::same_as<typename E::product,transformed<E>>){
auto[n,cut]=detail::conv_size_for(s);
T c0=a[0]*a[0];
E::extend_to(ta,n,a);
auto p=E::sq(ta,n);
auto tp=p;
detail::finish_linear<E>(std::move(p),n,s,cut,c0,std::span<T>(coeffs));
t=std::move(tp);
}else{
square<E>(a,ta,std::span<T>(coeffs));
}
}
template<engine E>vector<typename E::value_type>multiply(
const vector<typename E::value_type>&a,const vector<typename E::value_type>&b){
using T=typename E::value_type;
if(sz(a)==0||sz(b)==0)return{};
vector<T>r(sz(a)+sz(b)-1);
multiply<E>(std::span<const T>(a),std::span<const T>(b),std::span<T>(r));
return r;
}
template<engine E>vector<typename E::value_type>square(const vector<typename E::value_type>&a){
using T=typename E::value_type;
if(sz(a)==0)return{};
vector<T>r(2*sz(a)-1);
square<E>(std::span<const T>(a),std::span<T>(r));
return r;
}
namespace detail{
template<typename T,typename Op>
void emit_middle(std::span<T>buf,bool cut,int la,int lb,T c0,T ctop,std::span<T>out,Op op){
int m=la-lb+1;
T cn{};
if(cut){
cn=buf[0]-c0;
buf[lb-1]-=ctop;
}
int lim=min(sz(out),cut?m-1:m);
for(int t=0;t<lim;t++)op(out[t],buf[lb-1+t]);
if(cut&&sz(out)>=m)op(out[m-1],cn);
}
}
template<engine E,typename Op=assign_op>
void middle_product(std::span<const typename E::value_type>a,std::span<const typename E::value_type>b,
std::span<typename E::value_type>out,Op op={}){
using T=typename E::value_type;
if(sz(a)==0||sz(b)==0)return;
assert(sz(a)>=sz(b));
if(sz(a)==sz(b)){
T r{};
for(int i=0;i<sz(a);i++){
r+=a[i]*b[sz(b)-1-i];
}
if(sz(out)>0)op(out[0],r);
return;
}
auto[n,cut]=detail::conv_size_for(sz(a));
auto buf=buffer_pool<T>::get(n);
multiply_circular<E>(a,b,buf.span(),n);
detail::emit_middle<T>(buf.span(),cut,sz(a),sz(b),
a[0]*b[0],a[sz(a)-1]*b[sz(b)-1],out,op);
}
template<engine E>vector<typename E::value_type>middle_product(
std::span<const typename E::value_type>a,std::span<const typename E::value_type>b){
using T=typename E::value_type;
if(sz(a)==0||sz(b)==0)return{};
assert(sz(a)>=sz(b));
vector<T>r(size_t(sz(a)-sz(b)+1));
middle_product<E>(a,b,std::span<T>(r));
return r;
}
template<engine E,typename Op=assign_op>
void middle_product(std::span<const typename E::value_type>a,transformed<E>&ta,
std::span<const typename E::value_type>b,transformed<E>&tb,
std::span<typename E::value_type>out,Op op={}){
using T=typename E::value_type;
if(sz(a)==0||sz(b)==0)return;
assert(sz(a)>=sz(b));
if(sz(a)==sz(b)){
T r{};
for(int i=0;i<sz(a);i++){
r+=a[i]*b[sz(b)-1-i];
}
if(sz(out)>0)op(out[0],r);
return;
}
auto[n,cut]=detail::conv_size_for(sz(a));
E::extend_to(ta,n,a);
E::extend_to(tb,n,b);
auto buf=buffer_pool<T>::get(n);
E::finish(E::mul(ta,tb,n),buf.span());
detail::emit_middle<T>(buf.span(),cut,sz(a),sz(b),
a[0]*b[0],a[sz(a)-1]*b[sz(b)-1],out,op);
}
template<engine E>
vector<typename E::value_type>middle_product(std::span<const typename E::value_type>a,transformed<E>&ta,
std::span<const typename E::value_type>b,transformed<E>&tb){
using T=typename E::value_type;
if(sz(a)==0||sz(b)==0)return{};
assert(sz(a)>=sz(b));
vector<T>r(size_t(sz(a)-sz(b)+1));
middle_product<E>(a,ta,b,tb,std::span<T>(r));
return r;
}
}
// src/fft/online.hpp
namespace ecnerwala{
template<fft::engine E>struct online_multiplier{
using T=typename E::value_type;
int N;int i;
std::vector<T>f,g;
std::vector<T>res;
std::vector<fft::transformed<E>>f_blocks,g_blocks;
online_multiplier(int N_):N(N_),i(0),f(N,T{}),g(N,T{}),res(2*N+1,T{}){}
T peek(){
return res[i];
}
void push(T v_f,T v_g){
assert(i<N);
f[i]=v_f;
g[i]=v_g;
if(i==0){
res[0]+=v_f*v_g;
}else{
res[i]+=v_f*g[0];
res[i]+=f[0]*v_g;
for(int p=1,k=0;(i&(p-1))==(p-1);p<<=1,k++){
int lo1=p;
int lo2=i+1-p;
int s=2*p-1;
auto fb=std::span<const T>(f).subspan(p,p);
auto gb=std::span<const T>(g).subspan(p,p);
auto out=std::span<T>(res).subspan(lo1+lo2,s);
if(i==2*p-1){
f_blocks.emplace_back();
g_blocks.emplace_back();
fft::multiply<E>(fb,f_blocks[k],gb,g_blocks[k],out,fft::add_op{});
break;
}
fft::transformed<E>cf,cg;
fft::multiply_add2<E>(
fb,f_blocks[k],std::span<const T>(g).subspan(lo2,p),cg,
std::span<const T>(f).subspan(lo2,p),cf,gb,g_blocks[k],
out,fft::add_op{});
}
}
i++;
}
T back(){
return res[i-1];
}
};
template<fft::engine E>struct online_squarer{
using T=typename E::value_type;
int N;int i;
std::vector<T>f;
std::vector<T>res;
std::vector<fft::transformed<E>>f_blocks;
online_squarer(int N_):N(N_),i(0),f(N,T{}),res(2*N+1,T{}){}
T peek(){
return res[i];
}
void push(T v_f){
assert(i<N);
f[i]=v_f;
if(i==0){
res[0]+=v_f*v_f;
}else{
if constexpr(E::commutative)res[i]+=(v_f+v_f)*f[0];
else res[i]+=v_f*f[0]+f[0]*v_f;
for(int p=1,k=0;(i&(p-1))==(p-1);p<<=1,k++){
int lo1=p;
int lo2=i+1-p;
int s=2*p-1;
auto fb=std::span<const T>(f).subspan(p,p);
auto fw=std::span<const T>(f).subspan(lo2,p);
auto out=std::span<T>(res).subspan(lo1+lo2,s);
if(i==2*p-1){
f_blocks.emplace_back();
fft::square<E>(fb,f_blocks[k],out,fft::add_op{});
break;
}
fft::transformed<E>cw;
if constexpr(E::commutative){
fft::multiply<E>(fb,f_blocks[k],fw,cw,out,fft::add_twice_op{});
}else{
fft::multiply_add2<E>(fb,f_blocks[k],fw,cw,
fw,cw,fb,f_blocks[k],out,fft::add_op{});
}
}
}
i++;
}
T back(){
return res[i-1];
}
};
}
// src/fft/engines/real.hpp
namespace ecnerwala::fft::engines{
template<typename dbl=double>struct real{
using value_type=dbl;
static constexpr bool commutative=true;
using cnum=cplx<dbl>;
using core=fft_core<cnum>;
struct transformed{
vector<cnum>v;
int size()const{return 2*sz(v);}
};
using product=transformed;
static constexpr int unit_scale=0;
template<int A=0>using transformed_t=transformed;
template<int K=0>using product_t=product;
static int packed_size(int n){return std::max(n/2,1);}
static void pack(std::span<const dbl>a,std::span<cnum>c){
for(int i=0;i<sz(a);i++)(i&1?c[i/2].y:c[i/2].x)=a[i];
}
static cnum part(const transformed&f,int t,bool odd){
cnum g=conj(f.v[core::conj_index(t)]);
return odd?(f.v[t]-g)*cnum(0,dbl(-0.5)):(f.v[t]+g)*cnum(dbl(0.5));
}
static cnum retangle(cnum s0,cnum s1,int mo,int q){
cnum s=(s0+s1)*cnum(dbl(0.5));
cnum d=(s0-s1)*cnum(dbl(0.5))*core::inv_rt[mo+q];
return s+cnum(-d.y,d.x);
}
static transformed transform(std::span<const dbl>a,int n){
assert(sz(a)<=2*n);
transformed r;
r.v.assign(packed_size(n),cnum(0));
for(int i=0;i<sz(a);i++){
int j=i<n?i:i-n;
((j&1)?r.v[j/2].y:r.v[j/2].x)+=a[i];
}
core::forward(std::span<cnum>(r.v));
return r;
}
static void extend_to(transformed&t,int m,std::span<const dbl>coeffs){
assert(!(m&(m-1))&&sz(coeffs)<=2*m);
if(t.size()>=m)return;
if(t.size()==0){t=transform(coeffs,m);return;}
auto buf=buffer_pool<cnum>::get((sz(coeffs)+1)/2);
std::fill(buf.span().begin(),buf.span().end(),cnum(0));
pack(coeffs,buf.span());
while(t.size()<m){
int s=sz(t.v);
t.v.resize(2*s);
core::extend(
std::span<cnum>(t.v),
std::span<const cnum>(buf.span()).first(size_t(min(sz(buf.span()),2*s)))
);
}
}
static transformed downsample(const transformed&t,int n,bool odd){return half(t,n,odd);}
static transformed negate_arg(const transformed&t,int n){
int m=packed_size(n);
assert(n>=2&&sz(t.v)>=m);
transformed r;r.v.resize(m);
for(int j=0;j<m;j++)r.v[j]=conj(t.v[core::conj_index(j)]);
return r;
}
static transformed half(const transformed&f,int n,bool odd){
assert(n>=2&&f.size()>=2*n);
int mo=n/2;
core::init(2*mo);
transformed r;r.v.resize(mo);
for(int u=0;u<mo;u++){
r.v[u]=retangle(part(f,2*u,odd),part(f,2*u+1,odd),mo,core::brev(u,mo));
}
return r;
}
static product mul(const transformed&a,const transformed&b,int n){
int m=packed_size(n);
assert(a.size()>=n&&b.size()>=n);
core::init(2*m);
product p;p.v.resize(m);
for(int t=0;t<m;t++){
int k=core::brev(t,m);
cnum w=core::rt[m+k];
cnum xa=part(a,t,false),ya=part(a,t,true);
cnum xb=part(b,t,false),yb=part(b,t,true);
cnum p0=(xa+w*ya)*(xb+w*yb);
cnum p1=(xa-w*ya)*(xb-w*yb);
p.v[t]=retangle(p0,p1,m,k);
}
return p;
}
static product sq(const transformed&a,int n){return mul(a,a,n);}
static product mul2(
const transformed&a1,const transformed&b1,
const transformed&a2,const transformed&b2,
int n
){
int m=packed_size(n);
assert(a1.size()>=n&&b1.size()>=n&&a2.size()>=n&&b2.size()>=n);
core::init(2*m);
product p;p.v.resize(m);
for(int t=0;t<m;t++){
int k=core::brev(t,m);
cnum w=core::rt[m+k];
cnum xa1=part(a1,t,false),ya1=part(a1,t,true);
cnum xb1=part(b1,t,false),yb1=part(b1,t,true);
cnum xa2=part(a2,t,false),ya2=part(a2,t,true);
cnum xb2=part(b2,t,false),yb2=part(b2,t,true);
cnum p0=(xa1+w*ya1)*(xb1+w*yb1)+(xa2+w*ya2)*(xb2+w*yb2);
cnum p1=(xa1-w*ya1)*(xb1-w*yb1)+(xa2-w*ya2)*(xb2-w*yb2);
p.v[t]=retangle(p0,p1,m,k);
}
return p;
}
static product add(product&&a,const product&b){
assert(a.size()==b.size());
for(int i=0;i<sz(a.v);i++)a.v[i]=a.v[i]+b.v[i];
return std::move(a);
}
template<typename Op=assign_op>static void finish(product&&p,std::span<dbl>out,Op op={}){
int m=sz(p.v);
assert(sz(out)<=2*m);
core::inverse(std::span<cnum>(p.v));
dbl d=dbl(1)/dbl(m);
for(int i=0;i<sz(out);i++)op(out[i],(i&1?p.v[i/2].y:p.v[i/2].x)*d);
}
};
}
// src/fft/test_util.test.hpp
namespace ecnerwala{
namespace fft{
template<typename T>std::vector<T>multiply_slow(const std::vector<T>&a,const std::vector<T>&b){
if(a.empty()||b.empty())return{};
std::vector<T>res(a.size()+b.size()-1);
for(int i=0;i<int(a.size());i++){
for(int j=0;j<int(b.size());j++){
res[i+j]+=a[i]*b[j];
}
}
return res;
}
template<typename T>T rnd_val(std::mt19937&mt){
if constexpr(std::is_floating_point_v<T>)return T(int(mt()%1024));
else return T(mt());
}
template<typename T>void fill_rnd(std::vector<T>&v,std::mt19937&mt){
for(T&x:v)x=rnd_val<T>(mt);
}
template<typename T>void check_eq(const std::vector<T>&got,const std::vector<T>&want){
REQUIRE(got.size()==want.size());
for(int i=0;i<int(got.size());i++){
INFO("i = "<<i);
if constexpr(std::is_floating_point_v<T>)REQUIRE(llround(got[i])==llround(want[i]));
else REQUIRE(got[i]==want[i]);
}
}
#define ALL_ENGINES \
engines::ntt<modnum<998244353>>,engines::ntt<mod_goldilocks>,engines::real<double>,\
engines::split<modnum<int(1e9)+7>>,engines::crt<modnum<int(1e9)+7>>
#define MOD_ENGINES \
engines::ntt<modnum<998244353>>,engines::ntt<mod_goldilocks>,\
engines::split<modnum<int(1e9)+7>>,engines::crt<modnum<int(1e9)+7>>
}}
// src/fft/engines/algebras.test.cpp
namespace ecnerwala{
namespace fft{
using namespace std;
static_assert(engine<engines::matrix<engines::ntt<modnum<998244353>>,2>>);
static_assert(engine<engines::trunc<engines::ntt<modnum<998244353>>,3>>);
static_assert(engine<engines::matrix<engines::split<modnum<int(1e9)+7>>,2>>);
static_assert(engine<engines::trunc<engines::crt<modnum<int(1e9)+7>>,2>>);
static_assert(engine<engines::matrix_stable<engines::split<modnum<int(1e9)+7>>,3>>);
static_assert(engine<engines::trunc_stable<engines::crt<modnum<int(1e9)+7>>,3>>);
template<typename E,bool online,int N>
void test_matrix_engine(mt19937&mt){
using M=typename E::value_type;
using num=std::remove_reference_t<decltype(std::declval<M&>()[{0,0}])>;
auto rnd_mat=[&](){
M m;
for(int r=0;r<N;r++)for(int c=0;c<N;c++)m[{r,c}]=rnd_val<num>(mt);
return m;
};
for(int la:{1,2,3,17,33}){
for(int lb:{1,2,16,17}){
vector<M>a(la),b(lb);
for(M&m:a)m=rnd_mat();
for(M&m:b)m=rnd_mat();
INFO("la = "<<la<<", lb = "<<lb);
REQUIRE(multiply<E>(a,b)==multiply_slow(a,b));
}
}
int n=33;
vector<M>f(n);
for(M&m:f)m=rnd_mat();
auto slow=multiply_slow(f,f);
slow.resize(2*n,M{});
vector<M>got(2*n-1);
square<E>(span<const M>(f),span<M>(got));
REQUIRE(got==vector<M>(slow.begin(),slow.begin()+2*n-1));
if constexpr(online){
online_squarer<E>os(n);
for(int i=0;i<n;i++){
os.push(f[i]);
REQUIRE(os.back()==slow[i]);
}
}
}
TEMPLATE_TEST_CASE("matrix engine","[fft]",
engines::ntt<modnum<998244353>>,
engines::split<modnum<int(1e9)+7>>,
engines::crt<modnum<int(1e9)+7>>){
using IE=TestType;
constexpr int N=IE::unit_scale==0?3:2;
mt19937 mt(Catch::getSeed());
test_matrix_engine<engines::matrix<IE,N>,IE::unit_scale==0,N>(mt);
test_matrix_engine<engines::matrix_stable<IE,3>,true,3>(mt);
}
template<typename E,typename num,int N>
void test_trunc_series_engine(mt19937&mt){
using P=typename E::value_type;
auto rnd_p=[&](){
P p;
for(int i=0;i<N;i++)p[i]=rnd_val<num>(mt);
return p;
};
for(int la:{1,2,3,17,33}){
for(int lb:{1,2,16,17}){
vector<P>a(la),b(lb);
for(P&p:a)p=rnd_p();
for(P&p:b)p=rnd_p();
INFO("la = "<<la<<", lb = "<<lb);
REQUIRE(multiply<E>(a,b)==multiply_slow(a,b));
}
}
}
TEMPLATE_TEST_CASE("trunc_series engine","[fft]",
engines::ntt<modnum<998244353>>,
engines::split<modnum<int(1e9)+7>>,
engines::crt<modnum<int(1e9)+7>>){
using IE=TestType;
using num=typename IE::value_type;
constexpr int N=IE::unit_scale==0?3:2;
mt19937 mt(Catch::getSeed());
test_trunc_series_engine<engines::trunc<IE,N>,num,N>(mt);
test_trunc_series_engine<engines::trunc_stable<IE,3>,num,3>(mt);
}
}}
#pragma GCC diagnostic pop
// clang-format on
// @formatter:on
Back to top page