競技プログラミング用 C++ ライブラリ
components/fps_projection_blocked.hpp · GitHub で見る
共通ページ · 軽量実装なし
power projectionと合成逆元が使う Linux x86_64 / GCC 向けの内部実装です。呼び出し側で AVX2 対応を確認して使います。合成・射影の共通変換を読み込み、合成専用の処理は読み込みません。
二変数 Bostan–Mori の分子・分母を段階的に更新し、係数射影と重み付き射影を計算します。projection_log・projection_scratch・power_projection・weighted_projection・native_projection・native_weighted_projection を提供します。raw API の条件は合成・射影の内部 APIを参照してください。両方の演算が必要なら components/fps_composition.hpp を使えます。
対象の係数数を \(n\) として、時間は \(O(n\log^2 n)\)、追加空間は \(O(n)\) です。
#ifndef CP_COMPONENTS_FPS_PROJECTION_BLOCKED_HPP
#define CP_COMPONENTS_FPS_PROJECTION_BLOCKED_HPP
#include "fps_composition_common.hpp"
#if defined(__linux__) && defined(__x86_64__) && defined(__GNUC__) && !defined(__clang__)
#pragma GCC push_options
_Pragma("GCC target(\"avx2\")")
#pragma GCC optimize("O3")
namespace fps_detail::composition_poly {
namespace detail {
inline size_t projection_length(size_t n) { return std::bit_ceil(std::max<size_t>(n, 512)); }
inline void projection_direct(ConstWords g, Words a) {
const size_t n = a.size();
Word power[kComposeBase] = {1}, next[kComposeBase];
for (size_t k = 0; k < n; ++k) {
a[k] = power[n - 1];
for (size_t i = 0; i < n; ++i) {
std::uint64_t c = 0;
for (size_t j = 1; j <= i && j < g.size(); ++j) c += std::uint64_t(g[j]) * power[i - j] % kP;
next[i] = Word(c % kP);
}
std::copy_n(next, n, power);
}
}
inline Vec odd_lanes(Vec x, Vec y) {
const Vec t = _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps(x), _mm256_castsi256_ps(y), 0xDD));
return _mm256_permute4x64_epi64(t, 0xD8);
}
template <bool kAdd, class Op>
void even_terms(ConstWords b, size_t lo, size_t hi, std::ptrdiff_t offset,
Words out, const Op& op) {
const auto scalar = [&op](Word x) {
return Word(_mm_cvtsi128_si32(_mm256_castsi256_si128(op(_mm256_set1_epi32(int(x))))));
};
const auto put = [&out](size_t j, Word x) {
if constexpr (kAdd) out[j] = out[j] + x >= kP ? out[j] + x - kP : out[j] + x;
else out[j] = x;
};
const auto at = [offset](size_t j) { return std::ptrdiff_t(2 * j) + offset; };
const auto first = [&](size_t i) {
const std::ptrdiff_t d = std::ptrdiff_t(i) - offset;
return std::min(out.size(), d <= 0 ? size_t(0) : size_t(d + 1) / 2);
};
const size_t begin = first(lo), end = std::max(begin, first(hi));
if constexpr (!kAdd) {
std::fill(out.begin(), out.begin() + std::ptrdiff_t(begin), 0);
std::fill(out.begin() + std::ptrdiff_t(end), out.end(), 0);
}
size_t j = begin;
for (; j + 8 <= end && size_t(at(j)) + 16 <= b.size(); j += 8) {
const Word* from = b.data() + at(j);
Vec x = op(even_lanes(load_unaligned(from), load_unaligned(from + 8)));
if constexpr (kAdd) x = reduce(add(load_unaligned(out.data() + j), x), kP);
store_unaligned(out.data() + j, x);
}
for (; j < end; ++j) put(j, scalar(b[size_t(at(j))]));
}
inline void add_into(ConstWords x, ConstWords y, Words out) {
for (size_t i = 0; i < out.size(); i += 8) store(out.data() + i, reduce(add(load(x.data() + i), load(y.data() + i)), kP));
}
struct ProjectionBottom {
static constexpr bool kForward = true, kInverse = false;
const Tables* tables;
const Word* p;
Word *v, *w;
void operator()(Word* a, size_t count, size_t first) const {
ForwardBottom{tables->roots}(a, count, first);
for (size_t i = 0; i < count; i += 4) pairs(a + 32 * i, 2 * (first + i));
}
[[gnu::flatten]] void pairs(const Word* q, size_t k) const {
const PairWeights weights(*tables, k);
Vec va[4], vb[4], wa[4], wb[4];
leaves<false>(q, p + 16 * k, weights.s, va, wa);
leaves<true>(q + 8, p + 16 * k + 8, weights.s, vb, wb);
combine_pairs(va, vb, weights.inverse, v + 8 * k);
combine_pairs(wa, wb, weights.inverse, w + 8 * k);
}
template <bool kNegative>
[[gnu::noinline, gnu::flatten]] static void leaves(const Word* q, const Word* p, const Factors& s,
Vec (&v)[4], Vec (&w)[4]) {
Vec c[8], a[8], tc[8];
load_leaves(q, c), load_leaves(p, a);
scaled_leaves<kNegative>(c, s, 2, tc);
graeffe_leaf(c, tc, v);
product_leaf<1>(a, c, tc, w);
}
};
inline void inverse_lower(const Word* a, size_t h, const Group& w, Word* out) {
for (size_t i = 0; i < 8 * h; i += 8) {
const Vec f0 = load(a + i), f1 = load(a + i + 8 * h), f2 = load(a + i + 16 * h), f3 = load(a + i + 24 * h);
store(out + i, low(add(low(add(f0, f1)), low(add(f2, f3)))));
store(out + i + 8 * h, low(add(times(diff(f0, f1), w.y), times(diff(f2, f3), w.z))));
}
}
inline void forward_lower(const Word* in, size_t h, const Group& w, Word* a) {
for (size_t i = 0; i < 8 * h; i += 8) {
const Vec u = low(load(in + i)), b = load(in + i + 8 * h), yb = times(b, w.y), zb = times(b, w.z);
store(a + i, add(u, yb)), store(a + i + 8 * h, diff(u, yb));
store(a + i + 16 * h, add(u, zb)), store(a + i + 24 * h, diff(u, zb));
}
}
template <class Bottom>
class Doubling {
public:
Doubling(Words a, size_t block, const Tables& tables, const Bottom& bottom)
: a_(a.data()), half_(a.size() / 2), block_(block), b_(size_t(std::countr_zero(block)) - 1),
aligned_(std::countr_zero(block) % 2 == 0), tables_(tables), bottom_(bottom),
inverse_(tables.roots, tables.inverse_roots, InverseBottom{tables.inverse_roots, a.data()}),
forward_(tables.roots, tables.inverse_roots, bottom) {}
void run(Word y_scale, Word fix) const {
Word* const copy = a_ + half_;
const size_t nv = half_ / 8, chunk = std::max<size_t>(block_, 16);
for (size_t c = 0; c < nv; c += chunk) {
for (size_t j = c / block_; j < (c + chunk) / block_; ++j) {
inverse_x(a_ + 8 * block_ * j, j, copy + 8 * block_ * j);
forward_x(copy + 8 * block_ * j, j, a_ + 8 * block_ * j);
}
if (block_ < 16) bottom_(a_ + 8 * c, chunk / 4, c / 4);
}
inverse_y(copy, y_scale);
if (fix) {
for (size_t i = 0; i < 4 * block_; i += 8) store(copy + i, negate(load(copy + i)));
copy[0] = fix >= kP - copy[0] ? fix - (kP - copy[0]) : fix + copy[0];
}
forward_upper();
}
private:
void inverse_x(Word* a, size_t j, Word* out) const {
if (block_ == 4) {
inverse_.visit(a, 4, j);
std::copy_n(a, 16, out);
} else if (aligned_) {
const size_t h = block_ / 4;
for (size_t t = 0; t < 4; ++t) inverse_.visit(a + 8 * t * h, h, 4 * j + t);
inverse_lower(a, h, Group(tables_.inverse_roots, j), out);
} else {
const size_t h = block_ / 2;
inverse_.visit(a, h, 2 * j);
inverse_.visit(a + 8 * h, h, 2 * j + 1);
for (size_t i = 0; i < 8 * h; i += 8) store(out + i, low(add(load(a + i), load(a + i + 8 * h))));
}
}
void forward_x(const Word* in, size_t j, Word* a) const {
if (block_ == 4) {
std::copy_n(in, 16, a);
std::fill_n(a + 16, 16, 0);
} else if (aligned_) {
const size_t h = block_ / 4;
forward_lower(in, h, Group(tables_.roots, j), a);
if (h == 4) return bottom_(a, 4, 4 * j);
for (size_t t = 0; t < 4; ++t) forward_.visit(a + 8 * t * h, h, 4 * j + t);
} else {
const size_t h = block_ / 2;
for (size_t i = 0; i < 8 * h; i += 8) {
const Vec x = load(in + i);
store(a + i, x), store(a + i + 8 * h, x);
}
if (h == 4) return;
forward_.visit(a, h, 2 * j);
forward_.visit(a + 8 * h, h, 2 * j + 1);
}
}
void above(Word* a, size_t nv, size_t k) const {
if (nv == block_) return;
if (nv == 2 * block_) {
const Factor x = entry(tables_.inverse_roots, k);
for (size_t i = 0; i < 4 * block_; i += 8) {
const Vec u = load(a + i), v = load(a + i + 8 * block_);
store(a + i, low(add(u, v))), store(a + i + 8 * block_, times(diff(u, v), x));
}
return;
}
const size_t h = nv / 4;
for (size_t t = 0; t < 4; ++t) above(a + 8 * t * h, h, 4 * k + t);
inverse_columns(a, h, b_, tables_.inverse_roots, k);
}
void inverse_y(Word* a, Word scale) const {
const size_t nv = half_ / 8, width = size_t(1) << b_;
const bool zero = b_ <= 5;
Vec* const v = reinterpret_cast<Vec*>(a);
const Factor s(scale);
const auto scaled = [&s](Vec x) __attribute__((target("avx2"))) { return reduce(times(x, s), kP); };
const Vec z0 = _mm256_setzero_si256();
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
for (size_t t = 0; t < 4; ++t) above(a + 8 * t * h, h, t);
const Factor z(tables_.inverse_roots[1], tables_.inverse_roots[9]);
for (size_t c = 0; c < h; c += 2 * width) {
for (size_t j = c; j < c + width; ++j) {
const Vec x0 = v[j], x1 = v[j + h], x2 = v[j + 2 * h], x3 = v[j + 3 * h];
const Vec ab = low(add(x0, x1)), cd = low(add(x2, x3));
const Vec amb = low(diff(x0, x1)), cmd = times(diff(x2, x3), z);
v[j] = scaled(add(ab, cd)), v[j + h] = scaled(add(amb, cmd));
v[j + 2 * h] = scaled(diff(ab, cd)), v[j + 3 * h] = scaled(diff(amb, cmd));
}
if (zero)
for (size_t j = c + width; j < c + 2 * width; ++j) v[j] = v[j + h] = v[j + 2 * h] = v[j + 3 * h] = z0;
}
} else {
const size_t h = nv / 2;
above(a, h, 0);
above(a + 8 * h, h, 1);
for (size_t c = 0; c < h; c += 2 * width) {
for (size_t j = c; j < c + width; ++j) {
const Vec x0 = v[j], x1 = v[j + h];
v[j] = scaled(add(x0, x1)), v[j + h] = scaled(diff(x0, x1));
}
if (zero)
for (size_t j = c + width; j < c + 2 * width; ++j) v[j] = v[j + h] = z0;
}
}
}
void forward_upper() const {
const size_t nv = half_ / 4, width = size_t(1) << b_;
const Pruned<Bottom> pruned(tables_, bottom_, b_);
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
Vec* const v = reinterpret_cast<Vec*>(a_);
const Factor z(tables_.roots[1], tables_.roots[9]);
for (size_t c = 0; c < h; c += 2 * width)
for (size_t j = c; j < c + width; ++j) {
const Vec x = v[2 * h + j], y = times(v[3 * h + j], z);
v[2 * h + j] = add(x, y), v[3 * h + j] = diff(x, y);
}
pruned.forward(a_ + 16 * h, h, 2);
pruned.forward(a_ + 24 * h, h, 3);
} else {
pruned.forward(a_ + 4 * nv, nv / 2, 1);
}
}
Word* a_;
size_t half_, block_, b_;
bool aligned_;
const Tables& tables_;
Bottom bottom_;
Recursion<InverseBottom> inverse_;
Recursion<Bottom> forward_;
};
inline void projection_first_levels(const Transform& t, const Tables& tables, ConstWords g,
size_t n, Words q, Words p) {
const size_t m = q.size() / 4, h = m / 2, quarter = m / 4, e = m - n, size = std::min(n, g.size());
const auto identity = [&](Vec x) __attribute__((target("avx2"))) { return x; };
const auto negated = [&](Vec x) __attribute__((target("avx2"))) { return negate(x); };
const auto doubled = [&](Vec x) __attribute__((target("avx2"))) { return twice_mod(x); };
const auto minus_twice = [&](Vec x) __attribute__((target("avx2"))) { return negate(twice_mod(x)); };
const auto row = [h, quarter](Words a, size_t k) { return a.subspan(k * h, quarter); };
if (size > 0) {
q[0] = 0;
std::copy(g.begin() + 1, g.begin() + std::ptrdiff_t(size), q.begin() + 1);
}
forward_with(q.first(2 * m), size, tables, LevelBottom{&tables, q.data()});
inverse_with(q.first(m), tables, InverseBottom{tables.inverse_roots, q.data()}, graeffe_scale(std::countr_zero(m)),
Half::kBoth);
const Words q2 = p.subspan(7 * h, h), q1 = q.subspan(7 * h, h), p1 = q.subspan(4 * h, h);
std::copy_n(q.begin(), h, q2.begin());
even_terms<false>(g, 1, size, 0, q1, minus_twice);
if (e % 2) even_terms<false>(g, 1, size, 1 - std::ptrdiff_t(e), p1, negated);
else even_terms<false>(g, 1, size, 1 - std::ptrdiff_t(e), p1, identity);
const Words tq2 = q.first(m), tq1 = q.subspan(m, m), tp1 = p.first(m);
t.forward(tq2.first(h), 0, tq2);
t.forward(q1, 0, tq1);
t.forward(p1, 0, tp1);
Word* const g1 = p.data() + 4 * h;
Word* const g2 = p.data() + 5 * h;
Word* const e12 = p.data() + 6 * h;
Word* const o1 = q.data() + 5 * h;
Word* const o2 = q.data() + 6 * h;
const FirstLevelProducts<true> products{&tables, tq1.data(), tq2.data(), tp1.data(), {g1, g2, e12, o1, o2}};
for (size_t k = 0; k < m / 16; k += 8) products.pairs(k);
const Word scale = graeffe_scale(std::countr_zero(h));
for (Word* out : products.out)
inverse_with(Words(out, h), tables, InverseBottom{tables.inverse_roots, out}, scale, Half::kLower);
std::fill(p.begin(), p.begin() + std::ptrdiff_t(quarter), 0);
if (e % 2 && (e - 1) / 2 % 2) p[(e - 3) / 4] = 1;
even_terms<false>(p1, 0, h, 1, row(p, 1), identity);
std::copy_n(o1, quarter, p.begin() + std::ptrdiff_t(2 * h));
std::copy_n(o2, quarter, p.begin() + std::ptrdiff_t(3 * h));
if (e % 2) {
const size_t d = (e - 1) / 2;
const std::ptrdiff_t offset = 1 - std::ptrdiff_t(d);
const ConstWords b1 = q1, b2 = q2;
if (d % 2) {
even_terms<true>(b1, 0, h, offset, row(p, 1), identity);
even_terms<true>(b2, 0, h, offset, row(p, 2), identity);
} else {
even_terms<true>(b1, 0, h, offset, row(p, 1), negated);
even_terms<true>(b2, 0, h, offset, row(p, 2), negated);
}
}
std::fill(q.begin(), q.begin() + std::ptrdiff_t(quarter), 0);
q[0] = 1;
even_terms<false>(q1, 0, h, 0, row(q, 1), doubled);
even_terms<false>(q2, 0, h, 0, row(q, 2), doubled);
add_into(row(q, 2), ConstWords(g1, quarter), row(q, 2));
for (size_t i = 0; i < quarter; i += 8) store(q.data() + 3 * h + i, twice_mod(load(e12 + i)));
std::copy_n(g2, quarter, q.begin() + std::ptrdiff_t(4 * h));
}
inline void projection_weighted_first(const Transform& t, const Tables& tables,
ConstWords g, ConstWords weights,
Words q, Words p, Words work) {
const size_t m = q.size() / 4, h = m / 2, quarter = m / 4, n = weights.size();
projection_first_levels(t, tables, g, n, q, p);
auto x = work.first(2 * m), y = work.subspan(2 * m, 2 * m), z = work.subspan(4 * m, 2 * m);
std::fill(x.begin(), x.end(), 0u);
std::fill(y.begin(), y.end(), 0u);
for (size_t i = 0; i < n; ++i) x[m - 1 - i] = weights[i];
for (size_t i = 1; i < std::min(g.size(), n); ++i) y[i] = i % 2 && g[i] ? kP - g[i] : g[i];
t.forward(y);
t.cyclic_product(x.first(m), 0, z, y, Half::kLower);
auto p0 = work.first(h), p1 = work.subspan(h, h);
for (size_t i = 0; i < h; ++i) {
const auto value = z[2 * i + 1];
p1[i] = value ? kP - value : 0;
p0[i] = 2 * i + 1 < m && m - 2 - 2 * i < n ? weights[m - 2 - 2 * i] : 0;
}
auto tp0 = work.subspan(m, m), tp1 = work.subspan(2 * m, m);
auto tq1 = work.subspan(3 * m, m), tq2 = work.subspan(4 * m, m), product = work.subspan(5 * m, m);
for (size_t i = 0; i < h; ++i) {
const auto a = q[7 * h + i], b = p[7 * h + i];
tq1[i] = i % 2 && a ? kP - a : a;
tq2[i] = i % 2 && b ? kP - b : b;
}
t.forward(p0, 0, tp0);
t.forward(p1, 0, tp1);
t.forward(tq1.first(h), 0, tq1);
t.forward(tq2.first(h), 0, tq2);
for (size_t i = 0; i < quarter; ++i) p[i] = p0[2 * i + 1];
t.inverse_product(tp0, tq1, product, Half::kLower);
for (size_t i = 0; i < quarter; ++i) {
const auto value = product[2 * i + 1] + p1[2 * i + 1];
p[h + i] = value >= kP ? value - kP : value;
}
const Transform::Pair pairs[2] = {{tp0, tq2}, {tp1, tq1}};
t.inverse_product_sum(pairs, product, Half::kLower);
for (size_t i = 0; i < quarter; ++i) p[2 * h + i] = product[2 * i + 1];
t.inverse_product(tp1, tq2, product, Half::kLower);
for (size_t i = 0; i < quarter; ++i) p[3 * h + i] = product[2 * i + 1];
}
inline size_t projection_work(size_t m) {
const size_t eighth = m / 8;
return std::max(7 * Carve::words(eighth + 1) + 8 * Carve::words(eighth), Carve::words(m) + 2 * Carve::words(m / 2));
}
inline void projection_last_levels(const Transform& t, const Tables& tables, Words q,
Words p, Words w, Words a) {
const size_t m = q.size() / 4, eighth = m / 8, quarter = m / 4, h = m / 2;
Words c[8], pc[8];
{
Carve carve(w);
for (size_t k = 1; k < 8; ++k) c[k] = carve.take(eighth + 1);
for (size_t k = 0; k < 8; ++k) pc[k] = carve.take(eighth);
}
for (size_t r = 0; r < eighth; r += 8) {
Vec x[8];
#pragma GCC unroll 8
for (size_t i = 0; i < 8; ++i) x[i] = load(q.data() + 16 * (r + i));
transpose(x);
#pragma GCC unroll 7
for (size_t k = 1; k < 8; ++k) store(c[k].data() + r, k % 2 ? negate(x[k]) : x[k]);
}
for (size_t k = 1; k < 8; ++k) {
const Word x = q[16 * eighth + k];
c[k][eighth] = k % 2 && x ? kP - x : x;
}
for (size_t r = 0; r < eighth; r += 8) {
Vec x[8];
#pragma GCC unroll 8
for (size_t i = 0; i < 8; ++i) x[i] = load(p.data() + 16 * (r + i));
transpose(x);
#pragma GCC unroll 8
for (size_t k = 0; k < 8; ++k) store(pc[k].data() + r, x[k]);
}
Carve carve_q(q), carve_p(p);
Words tc[8], tp[7], twice[3], cn[4], pn[4];
for (size_t k = 1; k < 8; ++k) {
tc[k] = carve_q.take(quarter);
t.forward(c[k], 0, tc[k]);
}
for (size_t k = 0; k < 7; ++k) {
tp[k] = carve_p.take(quarter);
t.forward(pc[k], 0, tp[k]);
}
for (auto& s : twice) s = carve_q.take(quarter);
multiply_constant(tc[5], 2, twice[0]), multiply_constant(tc[3], kP - 2, twice[1]), multiply_constant(tc[4], kP - 2, twice[2]);
for (size_t k = 1; k < 4; ++k) cn[k] = carve_q.take(quarter + 1);
for (auto& s : pn) s = carve_p.take(quarter);
{
const Transform::Pair first[1] = {{tc[1], tc[1]}}, second[2] = {{tc[2], tc[2]}, {tc[1], twice[1]}},
third[3] = {{tc[3], tc[3]}, {tc[1], twice[0]}, {tc[2], twice[2]}};
t.inverse_product_sum(first, cn[1].first(quarter));
t.inverse_product_sum(second, cn[2].first(quarter));
t.inverse_product_sum(third, cn[3].first(quarter));
}
const Word weight[4] = {0, kP - 2, 2, kP - 2};
for (size_t k = 1; k < 4; ++k) {
const ConstWords linear = c[2 * k];
const Factor f(weight[k]);
for (size_t i = 0; i < eighth; i += 8)
store(cn[k].data() + i, canonical(add(load(cn[k].data() + i), reduce(times(load(linear.data() + i), f), kP))));
const std::uint64_t last = cn[k][eighth] + std::uint64_t(weight[k]) * linear[eighth];
cn[k][eighth] = Word(last % kP);
cn[k][quarter] = cn[k][0];
cn[k][0] = 0;
}
{
Transform::Pair pairs[7];
for (size_t j = 0; j < 4; ++j) {
for (size_t b = 1; b <= 2 * j + 1; ++b) pairs[b - 1] = {tp[2 * j + 1 - b], tc[b]};
const std::span<const Transform::Pair> sum(pairs, 2 * j + 1);
if (j < 2) t.inverse_product_sum(sum, pn[j]);
else if (j == 2) inverse_product_sum<5>(tables, pairs, pn[j], Half::kBoth);
else inverse_product_sum<7>(tables, pairs, pn[j], Half::kBoth);
add_into(pn[j].first(eighth), pc[2 * j + 1], pn[j].first(eighth));
}
}
Carve next_q(q), next_p(p), next_w(w);
Words tcn[4], tpn[3];
for (size_t k = 1; k < 4; ++k) {
tcn[k] = next_q.take(h);
t.forward(cn[k], 0, tcn[k]);
}
for (size_t j = 0; j < 3; ++j) {
tpn[j] = next_p.take(h);
t.forward(pn[j], 0, tpn[j]);
}
const Words square = next_q.take(h), negative_q = next_w.take(m);
t.inverse_product(tcn[1], tcn[1], square);
for (size_t i = 0; i < h; i += 8) {
const Vec x = i < quarter ? negate(twice_mod(load(cn[2].data() + i))) : _mm256_setzero_si256();
store(negative_q.data() + i, reduce(add(x, load(square.data() + i)), kP));
}
{
const Word x = negative_q[quarter] + minus_twice(cn[2][quarter]);
negative_q[quarter] = x >= kP ? x - kP : x;
negative_q[h] = negative_q[0];
negative_q[0] = 0;
std::fill(negative_q.begin() + std::ptrdiff_t(h + 1), negative_q.end(), 0);
}
const Words r1 = next_w.take(h), r3 = next_w.take(h);
t.inverse_product(tpn[0], tcn[1], r1);
add_into(r1.first(quarter), pn[1], r1.first(quarter));
const Transform::Pair pairs[3] = {{tpn[2], tcn[1]}, {tpn[1], tcn[2]}, {tpn[0], tcn[3]}};
t.inverse_product_sum(pairs, r3);
add_into(r3.first(quarter), pn[3], r3.first(quarter));
t.forward(negative_q);
const Words product = next_q.take(m);
t.cyclic_product(r1, 0, product, negative_q);
size_t k = 0;
for (; k + 8 <= std::min(a.size(), h); k += 8)
store_unaligned(a.data() + k, reduce(add(load(r3.data() + k), load(product.data() + k)), kP));
for (; k < a.size(); ++k) {
const Word x = (k < h ? r3[k] : 0) + product[k];
a[k] = x >= kP ? x - kP : x;
}
}
}
inline int projection_log(size_t n) {
if (n <= detail::kComposeBase) return Transform::kMinLog;
return std::countr_zero(detail::projection_length(n));
}
inline size_t projection_scratch(size_t n) {
using detail::Carve;
if (n <= detail::kComposeBase) return 0;
const size_t m = detail::projection_length(n);
return Arena::footprint(detail::Tables::words(m)) + 2 * Carve::words(4 * m) + Carve::words(detail::projection_work(m));
}
namespace detail {
template<bool Weighted>
inline void project(const Transform& t, ConstWords g, ConstWords weights,
Words a, Words scratch) {
const size_t n = a.size();
if constexpr (Weighted) {
assert(n > kComposeBase && weights.size() == n);
} else if (n <= kComposeBase) return projection_direct(g, a);
const size_t m = projection_length(n);
const int lg = std::countr_zero(m);
const Tables tables(scratch.first(Tables::words(m)), m);
Carve carve(scratch.subspan(Arena::footprint(Tables::words(m))));
const Words q = carve.take(4 * m), p = carve.take(4 * m), w = carve.take(projection_work(m));
if constexpr (Weighted) projection_weighted_first(t, tables, g, weights, q, p, scratch.last(6 * m));
else projection_first_levels(t, tables, g, n, q, p);
const ProjectionBottom bottom{&tables, p.data(), q.data(), p.data()};
const Word half_scale = blocked_ntt::detail::power(kP / 2 + 1, 31);
Word c = 1;
for (int s = 2; s + 3 < lg; ++s) {
const size_t stride = 2 * (m >> s), rows = size_t(1) << s;
const size_t pad = size_t(std::countr_zero(stride / 16));
if (s == 2) {
forward_pruned(p, rows * stride, pad, tables, ForwardBottom{tables.roots});
forward_pruned(q, (rows + 1) * stride, pad, tables, bottom);
}
if (s + 4 < lg) {
const size_t block = stride / 16;
const Word next =
blocked_ntt::detail::multiply_mod(blocked_ntt::detail::multiply_mod(blocked_ntt::detail::multiply_mod(c, c), half_scale), Word(block));
const Word y_scale = kInverseScales[0][s + 4];
Doubling<ForwardBottom>(p, block, tables, ForwardBottom{tables.roots}).run(y_scale, 0);
Doubling<ProjectionBottom>(q, block, tables, bottom).run(y_scale, next + next >= kP ? next + next - kP : next + next);
c = next;
} else {
const Word inverse_c = blocked_ntt::detail::power(c, kP - 2);
const Word scale = blocked_ntt::detail::multiply_mod(graeffe_scale(lg + 1), blocked_ntt::detail::multiply_mod(inverse_c, inverse_c));
inverse_pruned(q.first(2 * m), pad - 1, tables, InverseBottom{tables.inverse_roots, q.data()}, scale);
inverse_pruned(p.first(2 * m), pad - 1, tables, InverseBottom{tables.inverse_roots, p.data()}, scale);
next_level(q, stride / 2, 2 * rows);
}
}
projection_last_levels(t, tables, q, p, w, a);
}
}
inline void power_projection(const Transform& t, ConstWords g, Words a,
Words scratch) {
detail::project<false>(t, g, {}, a, scratch);
}
inline void weighted_projection(const Transform& t, ConstWords g,
ConstWords weights, Words a, Words scratch) {
detail::project<true>(t, g, weights, a, scratch);
}
inline void native_weighted_projection(const mint *g, size_t ng, const mint *weights,
std::vector<mint> &out, size_t n, size_t count) {
ng = std::min(ng, n);
const int lg = projection_log(n) + 1;
const size_t scratch_words = projection_scratch(n) + Arena::footprint(6 * detail::projection_length(n));
Arena arena(Transform::words(lg) + Arena::footprint(ng) + 2 * Arena::footprint(n) + Arena::footprint(scratch_words));
const Transform t(arena, lg);
auto a = arena.take(ng), b = arena.take(n), w = arena.take(n);
for (size_t i = 0; i < ng; ++i) a[i] = i ? coefficient_value(g[i]) : 0;
for (size_t i = 0; i < n; ++i) w[i] = std::min(weights[i].v, weights[i].v - mint::mod());
weighted_projection(t, a, w, b, arena.take(scratch_words));
append_fps_words<true>(out, b.data(), count);
}
inline void native_projection(const mint *g, size_t ng, std::vector<mint> &out, size_t n) {
ng = std::min(ng, n);
const int lg = projection_log(n);
Arena arena(Transform::words(lg) + Arena::footprint(ng) + Arena::footprint(n) +
Arena::footprint(projection_scratch(n)));
const Transform t(arena, lg);
auto a = arena.take(ng), b = arena.take(n);
for (size_t i = 0; i < ng; ++i) a[i] = coefficient_value(g[i]);
power_projection(t, a, b, arena.take(projection_scratch(n)));
append_fps_words<false>(out, b.data(), b.size());
}
}
#pragma GCC pop_options
#endif
#endif
#line 1 "components/fps_projection_blocked.hpp"
#line 1 "components/fps_composition_common.hpp"
#line 1 "components/fps_blocked.hpp"
#line 1 "components/fps_workspace.hpp"
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <new>
#if defined(__linux__)
#include <sys/mman.h>
#endif
namespace fps_detail {
struct FpsWordsDelete {
void *region = nullptr;
std::size_t bytes = 0;
void operator()(unsigned int *p) const {
#if defined(__linux__)
if (region) {
munmap(region, bytes);
return;
}
#endif
::operator delete[](p, std::align_val_t(64));
}
};
using FpsWords = std::unique_ptr<unsigned int[], FpsWordsDelete>;
inline FpsWords allocate_fps_words(std::size_t count) {
#if defined(__linux__) && defined(MADV_HUGEPAGE)
constexpr std::size_t page = 1 << 21;
if (count >= page / sizeof(unsigned int) &&
count <= (std::numeric_limits<std::size_t>::max() - 2 * page) / sizeof(unsigned int)) {
const std::size_t bytes = (count * sizeof(unsigned int) + page - 1) / page * page;
void *region = mmap(nullptr, bytes + page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (region != MAP_FAILED) {
const auto address = (reinterpret_cast<std::uintptr_t>(region) + page - 1) & ~(page - 1);
auto *p = ::new (reinterpret_cast<void *>(address)) unsigned int[count];
madvise(p, bytes, MADV_HUGEPAGE);
return FpsWords(p, FpsWordsDelete{region, bytes + page});
}
}
#endif
return FpsWords(new (std::align_val_t(64)) unsigned int[count]);
}
}
#line 1 "components/fps_output.hpp"
#line 1 "components/mint_arithmetic.hpp"
#line 1 "math/mint.hpp"
#include <cassert>
#include <istream>
#include <ostream>
#include <type_traits>
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC push_options
#pragma GCC optimize("O3")
#endif
namespace mint_detail {
template <class I>
constexpr bool mint_integral = std::is_integral_v<I>
#if defined(__SIZEOF_INT128__)
|| std::is_same_v<I, __int128> || std::is_same_v<I, unsigned __int128>
#endif
;
struct [[gnu::warn_unused]] Mint {
using mint = Mint;
unsigned v = 0;
[[nodiscard]] static constexpr unsigned mod() { return 998244353; }
[[nodiscard, gnu::always_inline]] static unsigned reduce(unsigned long long x) {
return (x + (unsigned long long)(unsigned(x) * 998244351u) * mod()) >> 32;
}
[[gnu::always_inline]] Mint() = default;
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
Mint(I x) {
long long r = x % (long long)mod();
if (r < 0) r += mod();
v = reduce((unsigned long long)r * 932051910);
}
[[nodiscard, gnu::always_inline]] static Mint raw(unsigned x) {
Mint a;
a.v = reduce((unsigned long long)x * 932051910);
return a;
}
[[nodiscard, gnu::always_inline]] static Mint word(unsigned x) {
Mint a;
a.v = x;
return a;
}
[[nodiscard, gnu::always_inline]] unsigned val() const {
unsigned r = reduce(v);
return r < mod() ? r : r - mod();
}
friend std::istream &operator>>(std::istream &is, Mint &x) {
long long value;
if (is >> value) x = Mint(value);
return is;
}
friend std::ostream &operator<<(std::ostream &os, const Mint &x) {
return os << x.val();
}
[[gnu::always_inline]] Mint &operator+=(Mint b) {
v += b.v;
if (v >= 2 * mod()) v -= 2 * mod();
return *this;
}
[[gnu::always_inline]] Mint &operator-=(Mint b) {
v -= b.v;
if (v >> 31) v += 2 * mod();
return *this;
}
[[gnu::always_inline]] Mint &operator*=(Mint b) {
v = reduce((unsigned long long)v * b.v);
return *this;
}
Mint &operator/=(Mint b) { return *this *= b.inv(); }
Mint &operator++() { return *this += Mint(1); }
Mint &operator--() { return *this -= Mint(1); }
Mint operator++(int) {
Mint a = *this;
++*this;
return a;
}
Mint operator--(int) {
Mint a = *this;
--*this;
return a;
}
[[nodiscard, gnu::always_inline]] Mint operator+() const { return *this; }
[[nodiscard, gnu::always_inline]] Mint operator-() const { return word(v ? 2 * mod() - v : 0); }
[[nodiscard, gnu::always_inline]] Mint operator+(Mint b) const { return Mint(*this) += b; }
[[nodiscard, gnu::always_inline]] Mint operator-(Mint b) const { return Mint(*this) -= b; }
[[nodiscard, gnu::always_inline]] Mint operator*(Mint b) const { return Mint(*this) *= b; }
[[nodiscard]] Mint operator/(Mint b) const { return Mint(*this) /= b; }
[[nodiscard, gnu::always_inline]] bool operator==(Mint b) const { return (v < mod() ? v : v - mod()) == (b.v < mod() ? b.v : b.v - mod()); }
[[nodiscard, gnu::always_inline]] bool operator!=(Mint b) const { return !(*this == b); }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator+(I a, Mint b) { return Mint(a) + b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator-(I a, Mint b) { return Mint(a) - b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator*(I a, Mint b) { return Mint(a) * b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator/(I a, Mint b) { return Mint(a) / b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend bool operator==(I a, Mint b) { return Mint(a) == b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend bool operator!=(I a, Mint b) { return Mint(a) != b; }
[[nodiscard]] Mint pow(long long n) const {
assert(n >= 0);
if (!n) return Mint(1);
Mint r(1), a = *this;
for (; n > 1; n >>= 1, a *= a)
if (n & 1) r *= a;
return r * a;
}
[[nodiscard, gnu::noinline]] Mint inv() const {
assert(*this != Mint(0));
Mint a2 = *this * *this, a3 = a2 * *this;
Mint a7 = a3 * a3 * *this;
Mint a31 = a7;
for (int i = 0; i < 2; ++i) a31 *= a31;
a31 *= a3;
Mint r = a7;
for (int i = 0; i < 3; ++i) r *= r;
r *= a3;
r *= r;
for (int j = 0; j < 4; ++j) {
for (int i = 0; i < 5; ++i) r *= r;
r *= a31;
}
for (int i = 0; i < 3; ++i) r *= r;
return r * a7;
}
};
} // namespace mint_detail
using mint = mint_detail::Mint;
using fps_mint = mint;
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC pop_options
#endif
#line 5 "components/mint_arithmetic.hpp"
#if defined(__x86_64__) || defined(__i386__)
#include <immintrin.h>
#endif
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC push_options
#pragma GCC optimize("O3")
#endif
namespace mint_detail {
struct Ops {
using T = mint;
typedef unsigned int U __attribute__((may_alias));
static constexpr U mod = 998244353, twice = 2 * mod, one = 301989884;
static U reduce(unsigned long long x) { return T::reduce(x); }
static U mul(U a, U b) { return reduce((unsigned long long)a * b); }
static U add(U a, U b) {
U c = a + b;
return c < twice ? c : c - twice;
}
static U sub(U a, U b) {
U c = a - b;
return c < twice ? c : c + twice;
}
static U in(U a) { return mul(a, 932051910); }
static U out(U a) {
a = reduce(a);
return a < mod ? a : a - mod;
}
static U quotient(U r) { return ((unsigned long long)r << 32) / mod; }
static U fixed_mul(U a, U r, U q) {
return a * r - U(((unsigned long long)a * q) >> 32) * mod;
}
#if defined(__x86_64__) || defined(__i386__)
static bool vectorized() {
#if defined(__AVX2__)
return true;
#else
static const bool available = __builtin_cpu_supports("avx2");
return available;
#endif
}
using I8 = __m256i;
__attribute__((target("avx2"))) static I8 load8(const U *a) {
return _mm256_loadu_si256(reinterpret_cast<const I8 *>(a));
}
__attribute__((target("avx2"))) static void store8(U *a, I8 x) {
_mm256_storeu_si256(reinterpret_cast<I8 *>(a), x);
}
__attribute__((target("avx2"), always_inline)) static inline I8 reduce8(I8 x, I8 y) {
I8 r = _mm256_set1_epi32(998244351), p = _mm256_set1_epi32(mod);
x = _mm256_add_epi64(x, _mm256_mul_epu32(_mm256_mul_epu32(x, r), p));
y = _mm256_add_epi64(y, _mm256_mul_epu32(_mm256_mul_epu32(y, r), p));
return _mm256_blend_epi32(_mm256_srli_epi64(x, 32), y, 0xaa);
}
__attribute__((target("avx2"))) static I8 mul8(I8 a, I8 b) {
return reduce8(_mm256_mul_epu32(a, b), _mm256_mul_epu32(_mm256_srli_epi64(a, 32), _mm256_srli_epi64(b, 32)));
}
__attribute__((target("avx2"))) static I8 fixed_mul8(I8 a, I8 r, I8 q) {
I8 x = _mm256_srli_epi64(_mm256_mul_epu32(a, q), 32);
I8 y = _mm256_mul_epu32(_mm256_srli_epi64(a, 32), q);
I8 t = _mm256_blend_epi32(x, y, 0xaa);
return _mm256_sub_epi32(_mm256_mullo_epi32(a, r),
_mm256_mullo_epi32(t, _mm256_set1_epi32(mod)));
}
__attribute__((target("avx2"))) static I8 fixed_even8(I8 a, I8 r, I8 q) {
I8 t = _mm256_srli_epi64(_mm256_mul_epu32(a, q), 32);
return _mm256_sub_epi64(_mm256_mul_epu32(a, r),
_mm256_mul_epu32(t, _mm256_set1_epi32(mod)));
}
__attribute__((target("avx2"))) static I8 add8(I8 a, I8 b) {
I8 x = _mm256_add_epi32(a, b);
return _mm256_min_epu32(x, _mm256_sub_epi32(x, _mm256_set1_epi32(twice)));
}
__attribute__((target("avx2"))) static I8 sub8(I8 a, I8 b) {
I8 x = _mm256_sub_epi32(a, b);
return _mm256_min_epu32(x, _mm256_add_epi32(x, _mm256_set1_epi32(twice)));
}
#endif
};
}
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC pop_options
#endif
#line 5 "components/fps_output.hpp"
#include <array>
#include <cstring>
#line 8 "components/fps_output.hpp"
#include <stdexcept>
#line 10 "components/fps_output.hpp"
#include <vector>
#if defined(__linux__) && defined(__x86_64__) && defined(__GNUC__) && !defined(__clang__)
#include <immintrin.h>
#include <sys/mman.h>
#pragma GCC push_options
_Pragma("GCC target(\"avx2\")")
#pragma GCC optimize("O3")
namespace fps_detail {
inline void prepare_fps_output(std::vector<mint> &out, std::size_t n) {
if (n <= out.capacity()) return;
out.reserve(n);
if (n - out.size() < (1u << 17)) return;
const auto first = reinterpret_cast<std::uintptr_t>(out.data() + out.size());
const auto end = reinterpret_cast<std::uintptr_t>(out.data() + n);
[[maybe_unused]] const auto advise = [first, end](std::uintptr_t alignment, int advice) {
const auto lo = (first + alignment - 1) & ~(alignment - 1), hi = end & ~(alignment - 1);
if (lo < hi) static_cast<void>(madvise(reinterpret_cast<void *>(lo), hi - lo, advice));
};
#if defined(MADV_HUGEPAGE)
advise(2u * 1024 * 1024, MADV_HUGEPAGE);
#endif
#if defined(MADV_POPULATE_WRITE)
advise(4096, MADV_POPULATE_WRITE);
#endif
}
template <bool montgomery>
inline void append_fps_words(std::vector<mint> &out, const unsigned *in, std::size_t n) {
static_assert(sizeof(mint) == sizeof(unsigned) && std::is_trivially_copyable_v<mint>);
if (n == 0) return;
if (n > out.max_size() - out.size()) throw std::length_error("fps output");
prepare_fps_output(out, out.size() + n);
std::array<mint, 64> batch;
std::size_t i = 0;
for (; i + 64 <= n; i += 64) {
if constexpr (montgomery) std::memcpy(static_cast<void *>(batch.data()), in + i, sizeof(batch));
else {
#pragma GCC unroll 8
for (int j = 0; j < 64; j += 8) {
const auto x = mint_detail::Ops::mul8(_mm256_loadu_si256(reinterpret_cast<const __m256i *>(in + i + j)), _mm256_set1_epi32(932051910u));
std::memcpy(static_cast<void *>(batch.data() + j), &x, sizeof(x));
}
}
out.insert(out.end(), batch.begin(), batch.end());
}
for (; i < n; ++i) {
if constexpr (montgomery) out.emplace_back(mint::word(in[i]));
else out.emplace_back(mint::raw(in[i]));
}
}
}
#pragma GCC pop_options
#endif
#line 6 "components/fps_blocked.hpp"
#include <algorithm>
#line 8 "components/fps_blocked.hpp"
#include <bit>
#line 11 "components/fps_blocked.hpp"
#include <cstdlib>
#line 13 "components/fps_blocked.hpp"
#include <span>
#line 16 "components/fps_blocked.hpp"
#if defined(__linux__) && defined(__x86_64__) && defined(__GNUC__) && !defined(__clang__)
#include <immintrin.h>
#pragma GCC push_options
_Pragma("GCC target(\"avx2\")")
#pragma GCC optimize("O3")
namespace fps_detail {
using Word = std::uint32_t;
using Words = std::span<Word>;
using ConstWords = std::span<const Word>;
using std::size_t;
namespace blocked_ntt::kernels {
using Vec = __m256i;
inline constexpr Word kP = 998244353, k2P = 2 * kP, kNI = 998244351;
}
namespace blocked_ntt {
inline constexpr Word kModulus = kernels::kP;
inline constexpr int kMaxLog = 25;
namespace detail {
using kernels::Vec, kernels::kP;
constexpr Word multiply_mod(Word x, Word y) {
return Word(std::uint64_t(x) * y % kP);
}
constexpr Word power(Word x, Word e) {
Word r = 1;
for (; e; e >>= 1, x = multiply_mod(x, x))
if (e & 1) r = multiply_mod(r, x);
return r;
}
constexpr Word quotient(Word w) { return Word((std::uint64_t(w) << 32) / kP); }
inline constexpr Word kR = Word((std::uint64_t(1) << 32) % kP);
inline constexpr int kRootCount = kMaxLog - 4;
inline constexpr auto kRoots = [] {
std::array<std::array<Word, kRootCount>, 2> t{};
for (int j = 0; j < kRootCount; ++j) {
t[0][j] = power(3, (kP - 1) >> (j + 2));
t[1][j] = power(t[0][j], kP - 2);
}
return t;
}();
inline Vec broadcast(Word x) { return _mm256_set1_epi32(int(x)); }
inline Vec add(Vec x, Vec y) { return _mm256_add_epi32(x, y); }
inline Vec diff(Vec x, Vec y) { return _mm256_sub_epi32(_mm256_add_epi32(x, broadcast(2 * kP)), y); }
inline Vec reduce(Vec x, Word bound) { return _mm256_min_epu32(x, _mm256_sub_epi32(x, broadcast(bound))); }
struct Factor {
Vec w, q;
explicit Factor(Word value) : w(broadcast(value)), q(broadcast(quotient(value))) {}
Factor(Word value, Word value_quotient) : w(broadcast(value)), q(broadcast(value_quotient)) {}
};
inline Vec multiply(Vec x, const Factor& f) { return mint_detail::Ops::fixed_mul8(x, f.w, f.q); }
inline size_t slot(size_t k) { return (k >> 3 << 4) | (k & 7); }
inline constexpr size_t table_words(int lg) { return (size_t(1) << lg) / 8 + 16; }
inline void build_table(Word* t, size_t count, const std::array<Word, kRootCount>& roots) {
t[slot(0)] = 1;
t[slot(0) + 8] = quotient(1);
for (int level = 0; level < kRootCount; ++level) {
const size_t h = size_t(1) << level;
if (h >= count) break;
const Word root = roots[level];
if (h < 8) {
for (size_t j = 0; j < h; ++j) {
const Word v = multiply_mod(t[slot(j)], root);
t[slot(h + j)] = v;
t[slot(h + j) + 8] = quotient(v);
}
continue;
}
const Factor by(root), to_r(kR);
for (size_t j = 0; j < h; j += 8) {
const Vec v = reduce(multiply(_mm256_load_si256(reinterpret_cast<const Vec*>(t + slot(j))), by), kP);
const Vec vr = reduce(multiply(v, to_r), kP);
_mm256_store_si256(reinterpret_cast<Vec*>(t + slot(h + j)), v);
_mm256_store_si256(reinterpret_cast<Vec*>(t + slot(h + j) + 8), _mm256_mullo_epi32(vr, broadcast(kernels::kNI)));
}
}
}
}
}
namespace blocked_ntt::kernels {
using namespace detail;
template<bool Inverse, bool Identity>
inline void radix4(Vec* a, size_t h, const Word* x, const Word* y) {
const Factor wx(Identity ? 1 : x[0], Identity ? 4 : x[8]);
const Factor wy(Identity ? 1 : y[0], Identity ? 4 : y[8]), wz(y[1], y[9]);
#pragma GCC unroll 1
for (size_t k = 0; k < h; ++k) {
const Vec f0 = a[k], f1 = a[k + h], f2 = a[k + 2 * h], f3 = a[k + 3 * h];
if constexpr (Inverse) {
const Vec ab = reduce(add(f0, f1), 2 * kP), cd = reduce(add(f2, f3), 2 * kP);
const Vec amb = Identity ? reduce(diff(f0, f1), 2 * kP) : multiply(diff(f0, f1), wy);
const Vec cmd = multiply(diff(f2, f3), wz);
a[k] = reduce(add(ab, cd), 2 * kP), a[k + h] = reduce(add(amb, cmd), 2 * kP);
if constexpr (Identity) {
a[k + 2 * h] = reduce(diff(ab, cd), 2 * kP);
a[k + 3 * h] = reduce(diff(amb, cmd), 2 * kP);
} else {
a[k + 2 * h] = multiply(diff(ab, cd), wx);
a[k + 3 * h] = multiply(diff(amb, cmd), wx);
}
} else {
const Vec lo = reduce(f0, 2 * kP), hi = reduce(f1, 2 * kP);
const Vec xc = Identity ? reduce(f2, 2 * kP) : multiply(f2, wx);
const Vec xd = Identity ? reduce(f3, 2 * kP) : multiply(f3, wx);
const Vec ac = reduce(add(lo, xc), 2 * kP), amc = reduce(diff(lo, xc), 2 * kP);
const Vec bd = Identity ? reduce(add(hi, xd), 2 * kP) : multiply(add(hi, xd), wy);
const Vec bmd = multiply(diff(hi, xd), wz);
a[k] = add(ac, bd), a[k + h] = diff(ac, bd);
a[k + 2 * h] = add(amc, bmd), a[k + 3 * h] = diff(amc, bmd);
}
}
}
inline void forward(Vec* a, size_t h, const Word* x, const Word* y) {
radix4<false, false>(a, h, x, y);
}
inline void inverse(Vec* a, size_t h, const Word* x, const Word* y) {
radix4<true, false>(a, h, x, y);
}
inline void forward_identity(Vec* a, size_t h, const Word* y) {
radix4<false, true>(a, h, y, y);
}
inline void inverse_identity(Vec* a, size_t h, const Word* y) {
radix4<true, true>(a, h, y, y);
}
}
namespace blocked_poly::kernels {
using Vec = __m256i;
inline constexpr Word kP = 998244353, k2P = 2 * kP;
[[gnu::noinline]] inline void forward_h4(Vec* a, size_t count, const Word* table, size_t k) {
for (size_t j = 0; j < count; ++j)
blocked_ntt::kernels::forward(a + 16 * j, 4, table + blocked_ntt::detail::slot(k + j), table + blocked_ntt::detail::slot(2 * (k + j)));
}
[[gnu::noinline]] inline void inverse_h4(Vec* a, size_t count, const Word* table, size_t k) {
for (size_t j = 0; j < count; ++j)
blocked_ntt::kernels::inverse(a + 16 * j, 4, table + blocked_ntt::detail::slot(k + j), table + blocked_ntt::detail::slot(2 * (k + j)));
}
}
namespace blocked_poly {
inline constexpr Word kModulus = blocked_ntt::kModulus;
enum class Half { kBoth, kLower, kUpper };
class Arena {
public:
explicit Arena(size_t words)
: storage_(allocate_fps_words(words)), next_(storage_.get()), end_(next_ + words) {}
Arena(const Arena &) = delete;
Arena &operator=(const Arena &) = delete;
static constexpr size_t footprint(size_t n) { return (n + 7) / 8 * 8 + 16; }
Words take(size_t n) {
if (size_t(end_ - next_) < footprint(n)) throw std::bad_alloc();
auto *start = next_;
next_ += footprint(n);
return {start, n};
}
bool zero_initialized() const { return storage_.get_deleter().region != nullptr; }
private:
FpsWords storage_;
Word *next_, *end_;
};
namespace detail {
using blocked_ntt::detail::add, blocked_ntt::detail::broadcast, blocked_ntt::detail::diff, blocked_ntt::detail::Factor, blocked_ntt::detail::kP, blocked_ntt::detail::kR, blocked_ntt::detail::reduce, blocked_ntt::detail::slot, blocked_ntt::detail::Vec;
inline constexpr auto kInverseScales = [] {
std::array<std::array<Word, blocked_ntt::kMaxLog + 1>, 2> s{};
for (int lg = 3; lg <= blocked_ntt::kMaxLog; ++lg) {
s[0][lg] = blocked_ntt::detail::power(Word(1) << (lg - 3), kP - 2);
s[1][lg] = blocked_ntt::detail::multiply_mod(s[0][lg], kR);
}
return s;
}();
inline Vec load(const Word* p) { return _mm256_load_si256(reinterpret_cast<const Vec*>(p)); }
inline void store(Word* p, Vec x) { _mm256_store_si256(reinterpret_cast<Vec*>(p), x); }
inline Vec low(Vec x) { return reduce(x, 2 * kP); }
inline Vec canonical(Vec x) { return reduce(reduce(x, 2 * kP), kP); }
inline Vec low_difference(Vec x, Vec y) {
const Vec t = _mm256_sub_epi32(x, y);
return _mm256_min_epu32(t, _mm256_add_epi32(t, broadcast(2 * kP)));
}
inline Vec difference(Vec x, Vec y) { return reduce(_mm256_sub_epi32(add(x, broadcast(kP)), y), kP); }
inline Vec times(Vec x, const Factor& w) { return blocked_ntt::detail::multiply(x, w); }
inline Factor entry(const Word* table, size_t k) {
const Word* e = table + slot(k);
return Factor(e[0], e[8]);
}
inline Factor leaf_weight(const Word* roots, size_t p) {
const Word* e = roots + slot(p >> 1);
return p & 1 ? Factor(kP - e[0], ~e[8]) : Factor(e[0], e[8]);
}
struct Group {
Factor x, y, z;
[[gnu::always_inline]] Group(const Word* table, size_t g)
: x(entry(table, g)), y(entry(table, 2 * g)), z(entry(table, 2 * g + 1)) {}
};
[[gnu::always_inline]] inline void forward_h1(Vec (&f)[4], const Group& w) {
const Vec a = low(f[0]), b = low(f[1]), c = times(f[2], w.x), d = times(f[3], w.x);
const Vec ac = low(add(a, c)), amc = low_difference(a, c);
const Vec bd = times(add(b, d), w.y), bmd = times(diff(b, d), w.z);
f[0] = add(ac, bd), f[1] = diff(ac, bd), f[2] = add(amc, bmd), f[3] = diff(amc, bmd);
}
[[gnu::always_inline]] inline void inverse_h1(Vec (&f)[4], const Group& w) {
const Vec ab = low(add(f[0], f[1])), cd = low(add(f[2], f[3]));
const Vec amb = times(diff(f[0], f[1]), w.y), cmd = times(diff(f[2], f[3]), w.z);
f[0] = low(add(ab, cd)), f[1] = low(add(amb, cmd));
f[2] = times(diff(ab, cd), w.x), f[3] = times(diff(amb, cmd), w.x);
}
struct alignas(64) Window {
Word word[17];
};
inline void fill_window(Window& window, Vec a, const Factor& w) {
window.word[16] = 0;
store(window.word + 8, a);
store(window.word, reduce(times(a, w), kP));
}
[[gnu::always_inline]] inline void fill_windows(Window (&window)[4], const Vec (&a)[4], const Word* roots,
size_t g) {
const Factor y = entry(roots, 2 * g), z = entry(roots, 2 * g + 1);
#pragma GCC unroll 4
for (int t = 0; t < 4; ++t) {
window[t].word[16] = 0;
store(window[t].word + 8, a[t]);
const Vec wa = times(a[t], t < 2 ? y : z);
store(window[t].word, reduce(t % 2 ? _mm256_sub_epi32(broadcast(2 * kP), wa) : wa, kP));
}
}
[[gnu::always_inline]] inline Vec leaf_product(const Window& window, const Word* b) {
Vec even = _mm256_setzero_si256(), odd = even;
#pragma GCC unroll 8
for (int i = 0; i < 8; ++i) {
const Vec bi = broadcast(b[i]);
const Vec lo = _mm256_loadu_si256(reinterpret_cast<const Vec*>(window.word + 8 - i));
const Vec hi = _mm256_loadu_si256(reinterpret_cast<const Vec*>(window.word + 9 - i));
even = _mm256_add_epi64(even, _mm256_mul_epu32(lo, bi));
odd = _mm256_add_epi64(odd, _mm256_mul_epu32(hi, bi));
}
return reduce(mint_detail::Ops::reduce8(even, odd), 2 * kP);
}
struct ForwardBottom {
static constexpr bool kForward = true, kInverse = false;
const Word* roots;
void operator()(Word* a, size_t count, size_t first) const {
for (size_t j = 0; j < count; ++j, a += 32) {
const Group w(roots, first + j);
Vec f[4] = {load(a), load(a + 8), load(a + 16), load(a + 24)};
forward_h1(f, w);
#pragma GCC unroll 4
for (int t = 0; t < 4; ++t) store(a + 8 * t, canonical(f[t]));
}
}
};
struct InverseBottom {
static constexpr bool kForward = false, kInverse = true;
const Word* inverse_roots;
const Word* in;
void operator()(Word* a, size_t count, size_t first) const {
const Word* from = in + 32 * first;
for (size_t j = 0; j < count; ++j, a += 32, from += 32) {
const Group w(inverse_roots, first + j);
Vec f[4] = {load(from), load(from + 8), load(from + 16), load(from + 24)};
inverse_h1(f, w);
for (int t = 0; t < 4; ++t) store(a + 8 * t, f[t]);
}
}
};
struct ProductBottom {
static constexpr bool kForward = true, kInverse = true;
const Word* roots;
const Word* inverse_roots;
const Word* b;
size_t origin = 0;
[[gnu::always_inline]] void prepare(const Word* a, size_t g, Window (&window)[4]) const {
Vec f[4] = {load(a), load(a + 8), load(a + 16), load(a + 24)};
forward_h1(f, Group(roots, g));
#pragma GCC unroll 4
for (int t = 0; t < 4; ++t) f[t] = canonical(f[t]);
fill_windows(window, f, roots, g);
}
[[gnu::always_inline]] void finish(Word* a, size_t g, const Window (&window)[4]) const {
Vec f[4];
#pragma GCC unroll 4
for (int t = 0; t < 4; ++t) f[t] = leaf_product(window[t], b + 8 * (4 * g + t - origin));
inverse_h1(f, Group(inverse_roots, g));
for (int t = 0; t < 4; ++t) store(a + 8 * t, f[t]);
}
void operator()(Word* a, size_t count, size_t first) const {
Window window[2][4];
prepare(a, first, window[0]);
for (size_t j = 0; j < count; ++j, a += 32) {
if (j + 1 < count) prepare(a + 32, first + j + 1, window[(j + 1) & 1]);
finish(a, first + j, window[j & 1]);
}
}
};
struct InverseProductBottom {
static constexpr bool kForward = false, kInverse = true;
ProductBottom product;
const Word* a;
[[gnu::always_inline]] void prepare(size_t g, Window (&window)[4]) const {
const Word* leaves = a + 32 * g;
const Vec f[4] = {load(leaves), load(leaves + 8), load(leaves + 16), load(leaves + 24)};
fill_windows(window, f, product.roots, g);
}
void operator()(Word* out, size_t count, size_t first) const {
Window window[2][4];
prepare(first, window[0]);
for (size_t j = 0; j < count; ++j, out += 32) {
if (j + 1 < count) prepare(first + j + 1, window[(j + 1) & 1]);
product.finish(out, first + j, window[j & 1]);
}
}
};
template <size_t K>
struct InverseProductSumBottom {
static constexpr bool kForward = false, kInverse = true;
std::array<InverseProductBottom, K> terms;
void prepare(size_t g, Window (&window)[K][4]) const {
for (size_t k = 0; k < K; ++k) terms[k].prepare(g, window[k]);
}
void operator()(Word* out, size_t count, size_t first) const {
Window window[2][K][4];
prepare(first, window[0]);
for (size_t j = 0; j < count; ++j, out += 32) {
if (j + 1 < count) prepare(first + j + 1, window[(j + 1) & 1]);
const size_t g = first + j;
Vec f[4];
#pragma GCC unroll 4
for (size_t t = 0; t < 4; ++t) {
f[t] = leaf_product(window[j & 1][0][t], terms[0].product.b + 8 * (4 * g + t - terms[0].product.origin));
for (size_t k = 1; k < K; ++k)
f[t] = low(add(f[t], leaf_product(window[j & 1][k][t], terms[k].product.b + 8 * (4 * g + t - terms[k].product.origin))));
}
inverse_h1(f, Group(terms[0].product.inverse_roots, g));
#pragma GCC unroll 4
for (size_t t = 0; t < 4; ++t) store(out + 8 * t, f[t]);
}
}
};
struct ForwardProductBottom {
static constexpr bool kForward = true, kInverse = false;
ProductBottom product;
void operator()(Word* a, size_t count, size_t first) const {
const Factor undo_montgomery(kR);
Window window[2][4];
product.prepare(a, first, window[0]);
for (size_t j = 0; j < count; ++j, a += 32) {
if (j + 1 < count) product.prepare(a + 32, first + j + 1, window[(j + 1) & 1]);
const Word* b = product.b + 8 * (4 * (first + j) - product.origin);
#pragma GCC unroll 4
for (int t = 0; t < 4; ++t)
store(a + 8 * t, reduce(times(leaf_product(window[j & 1][t], b + 8 * t), undo_montgomery), kP));
}
}
};
template <class Bottom>
class Recursion {
public:
Recursion(const Word* roots, const Word* inverse_roots, const Bottom& bottom)
: r_(roots), ir_(inverse_roots), bottom_(bottom) {}
void visit(Word* a, size_t nv, size_t k) const {
if (nv <= kTile) return tile(a, nv, k);
const size_t h = nv / 4;
if constexpr (Bottom::kForward) forward(a, h, k);
for (size_t t = 0; t < 4; ++t) visit(a + 8 * t * h, h, 4 * k + t);
if constexpr (Bottom::kInverse) inverse(a, h, k);
}
private:
static constexpr size_t kTile = 256;
void tile(Word* a, size_t nv, size_t k) const {
if constexpr (Bottom::kForward) {
for (size_t h = nv / 4; h >= 16; h /= 4)
for (size_t j = 0, g = k * (nv / (4 * h)); j < nv; j += 4 * h, ++g) forward(a + 8 * j, h, g);
if (nv >= 16) kernels::forward_h4(vectors(a), nv / 16, r_, k * (nv / 16));
}
bottom_(a, nv / 4, k * (nv / 4));
if constexpr (Bottom::kInverse) {
if (nv >= 16) kernels::inverse_h4(vectors(a), nv / 16, ir_, k * (nv / 16));
for (size_t h = 16; h < nv; h *= 4)
for (size_t j = 0, g = k * (nv / (4 * h)); j < nv; j += 4 * h, ++g) inverse(a + 8 * j, h, g);
}
}
static Vec* vectors(Word* a) { return reinterpret_cast<Vec*>(a); }
void forward(Word* a, size_t h, size_t k) const {
if (k == 0) return blocked_ntt::kernels::forward_identity(vectors(a), h, r_);
blocked_ntt::kernels::forward(vectors(a), h, r_ + slot(k), r_ + slot(2 * k));
}
void inverse(Word* a, size_t h, size_t k) const {
if (k == 0) return blocked_ntt::kernels::inverse_identity(vectors(a), h, ir_);
blocked_ntt::kernels::inverse(vectors(a), h, ir_ + slot(k), ir_ + slot(2 * k));
}
const Word *r_, *ir_;
Bottom bottom_;
};
class Source {
public:
Source(const Word* in, size_t size, size_t shift)
: in_(in), shift_(shift), end_(shift + size), first_((shift + 7) / 8), first_touched_(shift / 8) {
count_ = end_ / 8 > first_ ? end_ / 8 - first_ : 0;
touched_ = (end_ + 7) / 8 - first_touched_;
}
Vec operator()(size_t v) const {
if (v - first_ < count_) return _mm256_loadu_si256(reinterpret_cast<const Vec*>(in_ + (8 * v - shift_)));
if (v - first_touched_ >= touched_) return _mm256_setzero_si256();
return edge(in_, shift_, end_, v);
}
bool within(size_t lo, size_t hi) const {
return touched_ == 0 || (first_touched_ >= lo && first_touched_ + touched_ <= hi);
}
private:
[[gnu::noinline]] static Vec edge(const Word* in, size_t shift, size_t end, size_t v) {
alignas(32) Word x[8] = {};
for (size_t i = 8 * v; i < 8 * v + 8; ++i)
if (i >= shift && i < end) x[i - 8 * v] = in[i - shift];
return load(x);
}
const Word* in_;
size_t shift_, end_;
size_t first_, count_;
size_t first_touched_, touched_;
};
template <class Input>
[[gnu::always_inline]] inline void forward_top4_body(Input in, Vec* out, size_t h, const Word* roots) {
const Factor z(roots[1], roots[9]);
const Vec p = broadcast(kP);
if (in.within(0, 2 * h)) {
for (size_t j = 0; j < h; ++j) {
const Vec a = in(j), b = in(j + h), zb = times(b, z);
out[j] = add(a, b), out[j + h] = diff(a, b), out[j + 2 * h] = add(a, zb), out[j + 3 * h] = diff(a, zb);
}
return;
}
if (in.within(2 * h, 4 * h)) {
for (size_t j = 0; j < h; ++j) {
const Vec c = in(j + 2 * h), d = in(j + 3 * h);
const Vec mc = _mm256_sub_epi32(p, c), zmd = times(_mm256_sub_epi32(p, d), z);
out[j] = add(c, d), out[j + h] = diff(c, d), out[j + 2 * h] = add(mc, zmd), out[j + 3 * h] = diff(mc, zmd);
}
return;
}
for (size_t j = 0; j < h; ++j) {
const Vec a = in(j), b = in(j + h), c = in(j + 2 * h), d = in(j + 3 * h);
const Vec ac = add(a, c), amc = _mm256_sub_epi32(add(a, p), c);
const Vec bd = add(b, d), zbmd = times(_mm256_sub_epi32(add(b, p), d), z);
out[j] = add(ac, bd), out[j + h] = diff(ac, bd);
out[j + 2 * h] = add(amc, zbmd), out[j + 3 * h] = diff(amc, zbmd);
}
}
template <class Input>
[[gnu::always_inline]] inline void forward_top2_body(Input in, Vec* out, size_t h) {
const Vec p = broadcast(kP);
if (in.within(0, h)) {
for (size_t j = 0; j < h; ++j) out[j] = out[j + h] = in(j);
return;
}
if (in.within(h, 2 * h)) {
for (size_t j = 0; j < h; ++j) {
const Vec v = in(j + h);
out[j] = v, out[j + h] = _mm256_sub_epi32(p, v);
}
return;
}
for (size_t j = 0; j < h; ++j) {
const Vec u = in(j), v = in(j + h);
out[j] = add(u, v), out[j + h] = _mm256_sub_epi32(add(u, p), v);
}
}
[[gnu::noinline]] inline void forward_top4(Source in, Vec* out, size_t h, const Word* roots) {
forward_top4_body(in, out, h, roots);
}
[[gnu::noinline]] inline void forward_top2(Source in, Vec* out, size_t h) {
forward_top2_body(in, out, h);
}
template <class Bottom>
void radix2_halves(const Recursion<Bottom>& recursion, const Source& in, Word* a, size_t nv) {
const size_t h = nv / 2;
if constexpr (Bottom::kForward) forward_top2(in, reinterpret_cast<Vec*>(a), h);
recursion.visit(a, h, 0);
recursion.visit(a + 8 * h, h, 1);
}
inline void inverse_top4(Vec* f, size_t h, Half output, const Word* inverse_roots, const Factor& s) {
const Factor z(inverse_roots[1], inverse_roots[9]);
const auto scale = [&s](Vec x) __attribute__((target("avx2"))) { return reduce(times(x, s), kP); };
for (size_t j = 0; j < h; ++j) {
const Vec p0 = f[j], p1 = f[j + h], p2 = f[j + 2 * h], p3 = f[j + 3 * h];
const Vec ab = low(add(p0, p1)), cd = low(add(p2, p3));
const Vec amb = low(diff(p0, p1)), cmd = times(diff(p2, p3), z);
if (output != Half::kUpper) f[j] = scale(add(ab, cd)), f[j + h] = scale(add(amb, cmd));
if (output != Half::kLower) f[j + 2 * h] = scale(diff(ab, cd)), f[j + 3 * h] = scale(diff(amb, cmd));
}
}
inline void inverse_top2(Vec* f, size_t h, Half output, Word scale) {
const Factor s(scale);
for (size_t j = 0; j < h; ++j) {
const Vec lo = f[j], hi = f[j + h];
if (output != Half::kUpper) f[j] = reduce(times(add(lo, hi), s), kP);
if (output != Half::kLower) f[j + h] = reduce(times(diff(lo, hi), s), kP);
}
}
}
class Transform {
public:
static constexpr int kMinLog = 6, kMaxLog = blocked_ntt::kMaxLog;
static constexpr size_t words(int lg_max) { return Arena::footprint(2 * blocked_ntt::detail::table_words(lg_max)); }
Transform(Arena& arena, int lg_max) : lg_max_(lg_max) {
if (lg_max < kMinLog || lg_max > kMaxLog) std::abort();
const size_t table = blocked_ntt::detail::table_words(lg_max), entries = (size_t(1) << lg_max) / 16;
roots_ = arena.take(2 * table).data();
std::fill_n(roots_, 2 * table, 0u);
inverse_roots_ = roots_ + table;
blocked_ntt::detail::build_table(roots_, entries, blocked_ntt::detail::kRoots[0]);
blocked_ntt::detail::build_table(inverse_roots_, entries, blocked_ntt::detail::kRoots[1]);
}
int lg_max() const { return lg_max_; }
const Word* roots() const { return roots_; }
const Word* inverse_roots() const { return inverse_roots_; }
void forward(ConstWords in, size_t shift, Words out) const {
run(out, source(in, shift, out.size()), detail::ForwardBottom{roots_}, 0, Half::kBoth);
}
void forward(Words a) const { forward(a, 0, a); }
void forward_upper(ConstWords in, size_t shift, Words out) const {
using namespace detail;
check_length(2 * out.size());
const Source src = source(in, shift, out.size());
const detail::Recursion recursion(roots_, inverse_roots_, ForwardBottom{roots_});
const size_t nv = out.size() / 8;
auto* v = reinterpret_cast<Vec*>(out.data());
if (std::countr_zero(nv) % 2 == 0) {
for (size_t j = 0; j < nv; ++j) v[j] = src(j);
return recursion.visit(out.data(), nv, 1);
}
const size_t h = nv / 2;
const Factor z(roots_[1], roots_[9]);
for (size_t j = 0; j < h; ++j) {
const Vec a = src(j), zb = times(src(j + h), z);
v[j] = add(a, zb), v[j + h] = diff(a, zb);
}
recursion.visit(out.data(), h, 2);
recursion.visit(out.data() + 8 * h, h, 3);
}
void inverse(ConstWords in, Words out, Half output = Half::kBoth) const {
using namespace blocked_ntt::detail;
const Word scale = inverse_scale(out.size(), false);
run(out, detail::Source(nullptr, 0, 0), detail::InverseBottom{inverse_roots_, in.data()}, scale, output);
}
void inverse(Words a, Half output = Half::kBoth) const { inverse(a, a, output); }
void cyclic_product(ConstWords in, size_t shift, Words out,
ConstWords b, Half output = Half::kBoth, Word c = 1) const {
using namespace blocked_ntt::detail;
const Word scale = multiply_mod(inverse_scale(out.size(), true), c);
run(out, source(in, shift, out.size()), detail::ProductBottom{roots_, inverse_roots_, b.data()}, scale, output);
}
void cyclic_product(Words a, ConstWords b) const { cyclic_product(a, 0, a, b); }
void inverse_product(ConstWords a, ConstWords b, Words out,
Half output = Half::kBoth, Word c = 1) const {
using namespace blocked_ntt::detail;
const Word scale = multiply_mod(inverse_scale(out.size(), true), c);
const detail::ProductBottom product{roots_, inverse_roots_, b.data()};
run(out, detail::Source(nullptr, 0, 0), detail::InverseProductBottom{product, a.data()}, scale, output);
}
struct Pair {
ConstWords a, b;
};
void inverse_product_sum(std::span<const Pair> pairs, Words out, Half output = Half::kBoth) const {
switch (pairs.size()) {
case 1: return inverse_product(pairs[0].a, pairs[0].b, out, output);
case 2: return inverse_products<2>(pairs, out, output);
case 3: return inverse_products<3>(pairs, out, output);
default: std::abort();
}
}
template <class Bottom>
void inverse_with(const Bottom& bottom, Words out, Half output = Half::kBoth) const {
using namespace blocked_ntt::detail;
const Word scale = inverse_scale(out.size(), true);
run(out, detail::Source(nullptr, 0, 0), bottom, scale, output);
}
void forward_product(ConstWords in, size_t shift, Words out,
ConstWords b) const {
const detail::ProductBottom product{roots_, inverse_roots_, b.data()};
run(out, source(in, shift, out.size()), detail::ForwardProductBottom{product}, 0, Half::kBoth);
}
void multiply(Words a, ConstWords b) const {
products(a.data(), a.data(), b.data(), a.size(), [&](detail::Vec, detail::Vec p) __attribute__((target("avx2"))) { return p; });
}
void multiply_add(Words c, ConstWords a,
ConstWords b) const {
products(c.data(), a.data(), b.data(), a.size(), [&](detail::Vec c, detail::Vec p) __attribute__((target("avx2"))) { return detail::add(c, p); });
}
protected:
Word inverse_scale(size_t n, bool montgomery) const {
check_length(n);
return detail::kInverseScales[montgomery][std::countr_zero(n)];
}
void check_length(size_t n) const {
const int lg = std::countr_zero(n);
if (!std::has_single_bit(n) || lg < kMinLog || lg > lg_max_) std::abort();
}
template <size_t K>
void inverse_products(std::span<const Pair> pairs, Words out, Half output) const {
using namespace blocked_ntt::detail;
const Word scale = inverse_scale(out.size(), true);
detail::InverseProductSumBottom<K> bottom{};
for (size_t k = 0; k < K; ++k) bottom.terms[k] = {{roots_, inverse_roots_, pairs[k].b.data()}, pairs[k].a.data()};
run(out, detail::Source(nullptr, 0, 0), bottom, scale, output);
}
static detail::Source source(ConstWords in, size_t shift, size_t n) {
if (shift > n || in.size() > n - shift) std::abort();
return detail::Source(in.data(), in.size(), shift);
}
template <class Bottom>
void run(Words a, const detail::Source& in, const Bottom& bottom, Word scale,
Half output) const {
using namespace detail;
check_length(a.size());
const detail::Recursion recursion(roots_, inverse_roots_, bottom);
const size_t nv = a.size() / 8;
auto* v = reinterpret_cast<Vec*>(a.data());
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
if constexpr (Bottom::kForward) forward_top4(in, v, h, roots_);
for (size_t t = 0; t < 4; ++t) recursion.visit(a.data() + 8 * t * h, h, t);
if constexpr (Bottom::kInverse) inverse_top4(v, h, output, inverse_roots_, Factor(scale));
} else {
const size_t h = nv / 2;
radix2_halves(recursion, in, a.data(), nv);
if constexpr (Bottom::kInverse) inverse_top2(v, h, output, scale);
}
}
template <class Combine>
void products(Word* out, const Word* a, const Word* b, size_t n, Combine combine) const {
using namespace detail;
check_length(n);
const Factor undo_montgomery(kR);
Window window[2];
fill_window(window[0], load(a), leaf_weight(roots_, 0));
for (size_t p = 0; p < n / 8; ++p) {
if (p + 1 < n / 8) fill_window(window[(p + 1) & 1], load(a + 8 * p + 8), leaf_weight(roots_, p + 1));
const Vec product = reduce(times(leaf_product(window[p & 1], b + 8 * p), undo_montgomery), kP);
store(out + 8 * p, canonical(combine(load(out + 8 * p), product)));
}
}
int lg_max_;
Word *roots_, *inverse_roots_;
};
namespace detail {
inline Vec load_unaligned(const Word* p) { return _mm256_loadu_si256(reinterpret_cast<const Vec*>(p)); }
inline void store_unaligned(Word* p, Vec x) { _mm256_storeu_si256(reinterpret_cast<Vec*>(p), x); }
inline Vec montgomery(Vec a, Vec b) { return mint_detail::Ops::mul8(a, b); }
class Indices {
public:
explicit Indices(size_t k) {
alignas(32) Word x[8];
for (Word i = 0; i < 8; ++i) x[i] = Word((std::uint64_t(k + i) << 32) % kP);
value_ = load(x);
}
Vec value() const { return value_; }
void next() { value_ = reduce(add(value_, broadcast(kStep)), kP); }
private:
static constexpr Word kStep = Word((std::uint64_t(8) << 32) % kP);
Vec value_;
};
inline Word scalar_inverse(Word x) { return blocked_ntt::detail::power(x, kP - 2); }
template <class A>
void divide_by_index(size_t first, Words out, const A& a, Word factor = 1) {
constexpr size_t kLanes = 32;
const size_t n = out.size();
if (n == 0) return;
const size_t blocks = (n + kLanes - 1) / kLanes, last = kLanes * (blocks - 1);
const Vec one = broadcast(1), step = broadcast(kLanes);
Vec x[4];
for (int v = 0; v < 4; ++v)
x[v] = _mm256_add_epi32(broadcast(Word(first + 8 * v)), _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7));
const auto valid = [n](size_t at) __attribute__((target("avx2"))) {
const Vec lanes = _mm256_add_epi32(broadcast(Word(at)), _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7));
return _mm256_cmpgt_epi32(broadcast(Word(n)), lanes);
};
const auto masked = [&](Vec value, size_t at) __attribute__((target("avx2"))) { return at + 8 <= n ? value : _mm256_blendv_epi8(one, value, valid(at)); };
Vec prefix[4];
for (int v = 0; v < 4; ++v) prefix[v] = masked(x[v], 8 * v);
for (size_t b = kLanes; b < n; b += kLanes) {
for (int v = 0; v < 4; ++v) {
store_unaligned(out.data() + b - kLanes + 8 * v, prefix[v]);
x[v] = _mm256_add_epi32(x[v], step);
prefix[v] = montgomery(prefix[v], masked(x[v], b + 8 * v));
}
}
const Vec left = montgomery(prefix[0], prefix[1]), right = montgomery(prefix[2], prefix[3]);
alignas(32) Word total[8], inverse[8];
store(total, canonical(montgomery(left, right)));
Word running = 1;
for (size_t l = 0; l < 8; ++l) {
inverse[l] = running;
running = blocked_ntt::detail::multiply_mod(running, total[l]);
}
running = blocked_ntt::detail::multiply_mod(scalar_inverse(running), blocked_ntt::detail::multiply_mod(kR, factor));
for (size_t l = 8; l-- > 0;) {
inverse[l] = blocked_ntt::detail::multiply_mod(inverse[l], running);
running = blocked_ntt::detail::multiply_mod(running, total[l]);
}
const Vec inv_left = montgomery(load(inverse), right), inv_right = montgomery(load(inverse), left);
Vec inv[4] = {montgomery(inv_left, prefix[1]), montgomery(inv_left, prefix[0]),
montgomery(inv_right, prefix[3]), montgomery(inv_right, prefix[2])};
const auto put = [&](size_t at, Vec reciprocal) __attribute__((target("avx2"))) {
const Vec q = canonical(montgomery(reciprocal, a(at)));
if (at + 8 <= n) return store_unaligned(out.data() + at, q);
_mm256_maskstore_epi32(reinterpret_cast<int*>(out.data() + at), valid(at), q);
};
for (size_t b = last; b > 0; b -= kLanes) {
for (int v = 0; v < 4; ++v) {
const size_t at = b + 8 * v;
if (at < n) put(at, montgomery(load_unaligned(out.data() + at - kLanes), inv[v]));
inv[v] = montgomery(inv[v], masked(x[v], at));
x[v] = _mm256_sub_epi32(x[v], step);
}
}
for (int v = 0; v < 4; ++v)
if (8 * size_t(v) < n) put(8 * v, inv[v]);
}
}
inline Word mint_scalar(Word x) { return x; }
inline unsigned mint_scalar(mint x) { return std::min(x.v, x.v - mint::mod()); }
inline void copy_coefficients(const mint *in, Words out) {
for (size_t i = 0; i < out.size(); ++i) out[i] = mint_scalar(in[i]);
}
template <class T> inline detail::Vec coefficients(std::span<const T> f, size_t at) {
detail::Vec value;
std::memcpy(&value, f.data() + at, sizeof(value));
if constexpr (std::is_same_v<T, mint>) return detail::reduce(value, detail::kP);
else return value;
}
template <class T> inline void derivative_impl(std::span<const T> f, Words d) {
using namespace detail;
const size_t n = d.size(), full = std::min(n, f.size() > 0 ? f.size() - 1 : 0) / 8 * 8;
Indices index(1);
for (size_t i = 0; i < full; i += 8, index.next())
store_unaligned(d.data() + i, canonical(montgomery(coefficients(f, i + 1), index.value())));
for (size_t i = full; i < n; ++i)
d[i] = i + 1 < f.size() ? blocked_ntt::detail::multiply_mod(Word(i + 1), mint_scalar(f[i + 1])) : 0;
}
inline void derivative(ConstWords f, Words d) { derivative_impl(f, d); }
inline void divide_by_index(ConstWords a, size_t first, Words q) {
if (a.size() != q.size() || first == 0 || first + a.size() > kModulus) std::abort();
const size_t n = a.size();
detail::divide_by_index(first, q, [a, n](size_t at) __attribute__((target("avx2"))) {
if (at + 8 <= n) return detail::load_unaligned(a.data() + at);
alignas(32) Word rest[8] = {};
std::copy(a.begin() + at, a.end(), rest);
return detail::load(rest);
});
}
namespace detail {
inline constexpr size_t kInverseBase = 32;
inline Word direct_product(const Word* a, const Word* b, size_t n) {
std::uint64_t sums[4] = {};
size_t j = 0;
for (; j + 4 <= n; j += 4)
for (size_t k = 0; k < 4; ++k) sums[k] += std::uint64_t(a[j + k]) * b[n - 1 - j - k];
std::uint64_t sum = 0;
for (auto x : sums) sum += x % kP;
for (; j < n; ++j) sum += std::uint64_t(a[j]) * b[n - 1 - j];
return Word(sum % kP);
}
inline void inverse_direct(ConstWords f, Words g, Word inv0 = 0) {
using blocked_ntt::detail::multiply_mod;
if (!inv0) inv0 = blocked_ntt::detail::power(f[0], kP - 2);
g[0] = inv0;
for (size_t i = 1; i < g.size(); ++i) {
const size_t count = std::min(i, f.size() - 1);
const Word sum = direct_product(f.data() + 1, g.data() + i - count, count);
g[i] = multiply_mod(sum ? kP - sum : 0, inv0);
}
}
}
inline int inverse_log(size_t n) {
return std::max(Transform::kMinLog, int(std::bit_width(std::max<size_t>(n, 2) - 1)));
}
inline size_t inverse_scratch(size_t n) { return 2 * Arena::footprint(size_t(1) << inverse_log(n)); }
inline void inverse_step(const Transform& t, ConstWords f, ConstWords g,
Words to, Words gt, Words work, Word scale = detail::kP - 1) {
const size_t k = g.size();
t.forward(g, 0, gt);
t.cyclic_product(f.first(std::min(2 * k, f.size())), 0, work, gt, Half::kUpper);
t.cyclic_product(work.subspan(k), k, work, gt, Half::kUpper, scale);
if (to.data() != work.data() + k) std::copy_n(work.begin() + std::ptrdiff_t(k), to.size(), to.begin());
}
inline void inverse(const Transform& t, ConstWords f, Words g,
Words scratch) {
const size_t n = g.size();
size_t k = std::min(n, detail::kInverseBase);
detail::inverse_direct(f, g.first(k));
for (; k < n; k *= 2) {
const size_t len = 2 * k;
inverse_step(t, f, g.first(k), g.subspan(k, std::min(k, n - k)), scratch.first(len),
scratch.subspan(Arena::footprint(len), len));
}
}
namespace detail {
inline constexpr size_t kLogBase = 64;
inline constexpr size_t kLogBlocks = 4;
inline void log_direct(ConstWords f, Words g) {
using blocked_ntt::detail::multiply_mod;
Word a[kLogBase] = {}, inv[kLogBase] = {0, 1}, kg[kLogBase] = {};
std::copy_n(f.begin(), std::min(f.size(), g.size()), a);
for (size_t i = 2; i < g.size(); ++i) inv[i] = multiply_mod(kP - kP / Word(i), inv[kP % i]);
g[0] = 0;
for (size_t i = 1; i < g.size(); ++i) {
const Word sum = direct_product(kg + 1, a + 1, i - 1);
kg[i] = (multiply_mod(Word(i), a[i]) + kP - sum) % kP;
g[i] = multiply_mod(kg[i], inv[i]);
}
}
inline size_t log_block(size_t n) {
return std::max<size_t>(32, std::bit_ceil((n - 1 + kLogBlocks - 1) / kLogBlocks));
}
inline size_t log_blocks(size_t n) { return (n - 1 + log_block(n) - 1) / log_block(n); }
template <class T> inline void subtract_derivative(std::span<const T> f, size_t first, size_t count,
Word* out, size_t size) {
const size_t inside = f.size() > first + 1 ? std::min(count, f.size() - first - 1) : 0, full = inside / 8 * 8;
Indices index(first + 1);
for (size_t c = 0; c < full; c += 8, index.next()) {
const Vec d = canonical(montgomery(coefficients(f, first + 1 + c), index.value()));
store_unaligned(out + c, reduce(_mm256_sub_epi32(add(d, broadcast(kP)), load_unaligned(out + size + c)), kP));
}
for (size_t c = full; c < count; ++c) {
const Word d = c < inside ? blocked_ntt::detail::multiply_mod(Word(first + 1 + c), mint_scalar(f[first + 1 + c])) : 0;
out[c] = (d + kP - out[size + c]) % kP;
}
}
inline void subtract_derivative_impl(ConstWords f, size_t first, size_t count, Word* out, size_t size) {
subtract_derivative(f, first, count, out, size);
}
inline void subtract_from_derivative(ConstWords f, size_t first, size_t count, Word* out, size_t size) {
subtract_derivative_impl(f, first, count, out, size);
}
struct ToeplitzBottom {
static constexpr bool kForward = false, kInverse = true;
const Word *roots, *inverse_roots, *w1, *w2, *q0, *q1;
Word* w3;
[[gnu::always_inline]] void prepare(size_t g, Window (&window)[3][4]) const {
Vec sum[4], d1[4], d3[4];
#pragma GCC unroll 4
for (size_t t = 0; t < 4; ++t) {
const size_t leaf = 8 * (4 * g + t);
const Vec v2 = load(w2 + leaf);
sum[t] = reduce(add(load(q0 + leaf), load(q1 + leaf)), kP);
d1[t] = difference(load(w1 + leaf), v2);
d3[t] = difference(load(w3 + leaf), v2);
}
fill_windows(window[0], sum, roots, g);
fill_windows(window[1], d1, roots, g);
fill_windows(window[2], d3, roots, g);
}
void operator()(Word* out, size_t count, size_t first) const {
Window window[2][3][4];
prepare(first, window[0]);
for (size_t j = 0; j < count; ++j, out += 32) {
if (j + 1 < count) prepare(first + j + 1, window[(j + 1) & 1]);
const size_t g = first + j;
const auto& w = window[j & 1];
Vec f[4];
#pragma GCC unroll 4
for (size_t t = 0; t < 4; ++t) {
const size_t leaf = 8 * (4 * g + t);
const Vec m1 = leaf_product(w[0][t], w2 + leaf);
f[t] = low(add(m1, leaf_product(w[1][t], q1 + leaf)));
store(w3 + leaf, low(add(m1, leaf_product(w[2][t], q0 + leaf))));
}
inverse_h1(f, Group(inverse_roots, g));
#pragma GCC unroll 4
for (size_t t = 0; t < 4; ++t) store(out + 8 * t, f[t]);
}
}
};
struct SideProductBottom {
static constexpr bool kForward = false, kInverse = true;
InverseProductBottom term;
const Word* side;
void operator()(Word* out, size_t count, size_t first) const {
Window window[2][4];
term.prepare(first, window[0]);
for (size_t j = 0; j < count; ++j, out += 32) {
if (j + 1 < count) term.prepare(first + j + 1, window[(j + 1) & 1]);
const size_t g = first + j;
Vec f[4];
#pragma GCC unroll 4
for (size_t t = 0; t < 4; ++t) {
const size_t leaf = 8 * (4 * g + t);
f[t] = low(add(leaf_product(window[j & 1][t], term.product.b + (leaf - 8 * term.product.origin)), load(side + leaf)));
}
inverse_h1(f, Group(term.product.inverse_roots, g));
#pragma GCC unroll 4
for (size_t t = 0; t < 4; ++t) store(out + 8 * t, f[t]);
}
}
};
inline size_t log_buffers(size_t n) { return std::max<size_t>(log_blocks(n) + 2, 3); }
inline Vec mint_load(std::span<const mint> f, size_t at) {
if (at + 8 <= f.size()) {
Vec value;
std::memcpy(&value, f.data() + at, sizeof(value));
return reduce(value, kP);
}
alignas(32) unsigned values[8] = {};
for (size_t i = 0; i < 8 && at + i < f.size(); ++i) values[i] = mint_scalar(f[at + i]);
return load(values);
}
class MintSource {
public:
explicit MintSource(std::span<const mint> f) : f_(f), full_(f.size() / 8), touched_((f.size() + 7) / 8) {}
Vec operator()(size_t v) const {
if (v < full_) {
Vec value;
std::memcpy(&value, f_.data() + 8 * v, sizeof(value));
return reduce(value, kP);
}
if (v >= touched_) return _mm256_setzero_si256();
return edge(f_, 8 * v);
}
bool within(size_t lo, size_t hi) const { return touched_ == 0 || (lo == 0 && touched_ <= hi); }
private:
[[gnu::noinline]] static Vec edge(std::span<const mint> f, size_t at) { return mint_load(f, at); }
std::span<const mint> f_;
size_t full_, touched_;
};
[[gnu::noinline]] inline void mint_forward_top4(MintSource in, Vec* out, size_t h, const Word* roots) {
forward_top4_body(in, out, h, roots);
}
[[gnu::noinline]] inline void mint_forward_top2(MintSource in, Vec* out, size_t h) {
forward_top2_body(in, out, h);
}
inline void mint_forward(const Transform& t, std::span<const mint> f, Words out) {
const MintSource in(f);
const detail::Recursion recursion(t.roots(), t.inverse_roots(), ForwardBottom{t.roots()});
const size_t nv = out.size() / 8;
auto* v = reinterpret_cast<Vec*>(out.data());
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
mint_forward_top4(in, v, h, t.roots());
for (size_t q = 0; q < 4; ++q) recursion.visit(out.data() + 8 * q * h, h, q);
} else {
const size_t h = nv / 2;
mint_forward_top2(in, v, h);
recursion.visit(out.data(), h, 0);
recursion.visit(out.data() + 8 * h, h, 1);
}
}
inline void mint_derivative(std::span<const mint> f, Words d) { derivative_impl(f, d); }
inline void mint_subtract_from_derivative(std::span<const mint> f, size_t first, size_t count, Word* out, size_t size) {
subtract_derivative(f, first, count, out, size);
}
struct NoLogTransform {
void operator()(size_t, ConstWords) const {}
};
template <class Input, class Sink, class Transformed>
[[gnu::always_inline]] inline void log_derivative(const Transform& t, Input f, size_t n,
Words scratch, ConstWords prefix, const Sink& sink, const Transformed& transformed = {}) {
const size_t k = log_block(n), len = 2 * k, blocks = log_blocks(n);
const auto buffer = [&scratch, len](size_t i) __attribute__((target("avx2"))) { return scratch.subspan(i * Arena::footprint(len), len); };
const Words ht = buffer(0), q0 = buffer(blocks);
const auto window = [&buffer](size_t t) __attribute__((target("avx2"))) { return buffer(t); };
inverse(t, prefix, ht.first(k), scratch.subspan(Arena::footprint(len), inverse_scratch(k)));
t.forward(ht.first(k), 0, ht);
for (size_t s = 1; s < blocks; ++s) {
const size_t from = std::min(f.size(), (s - 1) * k);
if constexpr (std::is_same_v<Input, std::span<const mint>>) mint_forward(t, f.subspan(from, std::min(f.size() - from, len)), window(s));
else t.forward(f.subspan(from, std::min(f.size() - from, len)), 0, window(s));
}
for (size_t j = 0; j < blocks; ++j) {
const size_t first = j * k, count = std::min(k, n - 1 - first);
const Words work = j == 0 ? q0 : buffer(blocks + 1);
if (j == 0) {
if constexpr (std::is_same_v<Input, std::span<const mint>>) mint_derivative(f.first(std::min(f.size(), count + 1)), work.first(count));
else derivative_impl(f.first(std::min(f.size(), count + 1)), work.first(count));
} else {
if (j == 1) {
t.inverse_product(window(1), q0, work, Half::kUpper);
} else if (blocks == 3) {
const Transform::Pair pairs[2] = {{window(2), q0}, {window(1), work}};
t.inverse_product_sum(pairs, work, Half::kUpper);
} else if (j == 2) {
t.inverse_with(ToeplitzBottom{t.roots(), t.inverse_roots(), window(1).data(), window(2).data(), q0.data(),
work.data(), window(3).data()},
work, Half::kUpper);
} else {
const InverseProductBottom product{{t.roots(), t.inverse_roots(), work.data()}, window(1).data()};
t.inverse_with(SideProductBottom{product, window(3).data()}, work, Half::kUpper);
}
if constexpr (std::is_same_v<Input, std::span<const mint>>) mint_subtract_from_derivative(f, first, count, work.data(), k);
else subtract_derivative_impl(f, first, count, work.data(), k);
}
t.cyclic_product(work.first(count), 0, work, ht, Half::kLower);
sink(first, ConstWords(work.first(count)));
if (j + 1 < blocks) {
t.forward(work.first(k), 0, work);
transformed(j, ConstWords(work));
}
}
}
template <class Sink, class Transformed = NoLogTransform>
[[gnu::always_inline]] inline void log_derivative(const Transform& t, ConstWords f, size_t n,
Words scratch, const Sink& sink, const Transformed& transformed = {}) {
log_derivative(t, f, n, scratch, f, sink, transformed);
}
template <class Sink, class Transformed = NoLogTransform>
[[gnu::always_inline]] inline void mint_log_derivative(const Transform& t, std::span<const mint> f, size_t n,
Words scratch, ConstWords prefix, const Sink& sink, const Transformed& transformed = {}) {
log_derivative(t, f, n, scratch, prefix, sink, transformed);
}
inline int log_derivative_log(size_t n) { return std::countr_zero(2 * log_block(n)); }
inline size_t log_derivative_scratch(size_t n) { return log_buffers(n) * Arena::footprint(2 * log_block(n)); }
}
inline int log_log(size_t n) {
return n <= detail::kLogBase ? Transform::kMinLog : std::countr_zero(2 * detail::log_block(n));
}
inline size_t log_scratch(size_t n) {
return n <= detail::kLogBase ? 0 : detail::log_buffers(n) * Arena::footprint(2 * detail::log_block(n));
}
inline void log(const Transform& t, ConstWords f, Words g,
Words scratch) {
using namespace detail;
const size_t n = g.size();
if (n <= kLogBase) return log_direct(f, g);
log_derivative(t, f, n, scratch, [g](size_t first, ConstWords q) __attribute__((target("avx2"))) {
detail::divide_by_index(first + 1, g.subspan(first + 1, q.size()), [q](size_t i) __attribute__((target("avx2"))) { return load(q.data() + i); });
});
g[0] = 0;
}
inline void native_log(const mint *in, size_t input, std::vector<mint> &out, size_t n, bool integrate) {
prepare_fps_output(out, n);
out.emplace_back(0);
const size_t c = std::min(input, n);
const int lg = detail::log_derivative_log(n);
const bool direct = n >= 262144;
const auto prefix_size = direct ? std::min(c, detail::log_block(n)) : c;
Arena arena(Transform::words(lg) + Arena::footprint(prefix_size) + Arena::footprint(log_scratch(n)));
const Transform t(arena, lg);
auto f = arena.take(prefix_size), scratch = arena.take(log_scratch(n));
copy_coefficients(in, f);
const auto sink = [&out, integrate, n](size_t first, ConstWords q) __attribute__((target("avx2"))) {
if (integrate) {
auto *work = const_cast<Word *>(q.data()) + detail::log_block(n);
detail::divide_by_index(first + 1, {work, q.size()}, [q](size_t i) __attribute__((target("avx2"))) { return detail::load(q.data() + i); }, blocked_ntt::detail::kR);
}
const auto *values = integrate ? q.data() + detail::log_block(n) : q.data();
if (integrate) append_fps_words<true>(out, values, q.size());
else append_fps_words<false>(out, values, q.size());
};
if (direct) detail::mint_log_derivative(t, {in, c}, n, scratch, f, sink);
else detail::log_derivative(t, f, n, scratch, sink);
}
}
}
#pragma GCC pop_options
#endif
#line 5 "components/fps_composition_common.hpp"
#if defined(__linux__) && defined(__x86_64__) && defined(__GNUC__) && !defined(__clang__)
#pragma GCC push_options
_Pragma("GCC target(\"avx2\")")
#pragma GCC optimize("O3")
namespace fps_detail::composition_poly {
using Word = std::uint32_t;
using Words = std::span<Word>;
using ConstWords = std::span<const Word>;
using std::size_t, blocked_poly::Arena, blocked_poly::Transform, blocked_poly::Half, blocked_poly::kModulus;
namespace kernels {
using Vec = __m256i;
namespace column_detail = blocked_poly::detail;
template<bool Inverse>
[[gnu::noinline]] void columns(Vec *a, size_t h, size_t width,
const Word *x, const Word *y) {
using namespace column_detail;
const Factor wx(x[0], x[8]), wy(y[0], y[8]), wz(y[1], y[9]);
for (size_t c = 0; c < h; c += 2 * width)
for (size_t j = c; j < c + width; ++j) {
const Vec f0 = a[j], f1 = a[j + h], f2 = a[j + 2 * h], f3 = a[j + 3 * h];
if constexpr (Inverse) {
const Vec ab = low(add(f0, f1)), cd = low(add(f2, f3));
const Vec amb = times(diff(f0, f1), wy), cmd = times(diff(f2, f3), wz);
a[j] = low(add(ab, cd)), a[j + h] = low(add(amb, cmd));
a[j + 2 * h] = times(diff(ab, cd), wx), a[j + 3 * h] = times(diff(amb, cmd), wx);
} else {
const Vec lo = low(f0), hi = low(f1), xc = times(f2, wx), xd = times(f3, wx);
const Vec ac = low(add(lo, xc)), amc = low_difference(lo, xc);
const Vec bd = times(add(hi, xd), wy), bmd = times(diff(hi, xd), wz);
a[j] = add(ac, bd), a[j + h] = diff(ac, bd);
a[j + 2 * h] = add(amc, bmd), a[j + 3 * h] = diff(amc, bmd);
}
}
}
inline void forward_columns(Vec *a, size_t h, size_t width, const Word *x, const Word *y) {
columns<false>(a, h, width, x, y);
}
inline void inverse_columns(Vec *a, size_t h, size_t width, const Word *x, const Word *y) {
columns<true>(a, h, width, x, y);
}
inline void forward_even_columns(Vec *a, size_t h, const Word *x, const Word *y) {
columns<false>(a, h, 1, x, y);
}
inline void inverse_even_columns(Vec *a, size_t h, const Word *x, const Word *y) {
columns<true>(a, h, 1, x, y);
}
}
namespace detail {
using namespace blocked_poly::detail;
using blocked_poly::detail::times;
struct Factors {
Vec w, q;
};
inline Vec times(Vec x, const Factors& w) {
const Vec even = _mm256_srli_epi64(_mm256_mul_epu32(x, w.q), 32);
const Vec odd = _mm256_mul_epu32(_mm256_srli_epi64(x, 32), _mm256_srli_epi64(w.q, 32));
const Vec q = _mm256_blend_epi32(even, odd, 0xAA);
return _mm256_sub_epi32(_mm256_mullo_epi32(x, w.w), _mm256_mullo_epi32(q, broadcast(kP)));
}
inline constexpr auto kInverseScales = [] {
std::array<std::array<Word, blocked_ntt::kMaxLog + 1>, 2> s{};
for (int lg = 3; lg <= blocked_ntt::kMaxLog; ++lg) {
s[0][lg] = blocked_ntt::detail::power(Word(1) << (lg - 3), kP - 2);
s[1][lg] = blocked_ntt::detail::multiply_mod(s[0][lg], kR);
}
return s;
}();
inline Factors entries(const Word* table, size_t k) {
const Word* e = table + slot(k);
return {load(e), load(e + 8)};
}
inline constexpr size_t kComposeBase = 32;
constexpr Word minus_twice(Word x) {
const Word y = x ? 2 * kP - 2 * x : 0;
return y >= kP ? y - kP : y;
}
inline Vec negate(Vec x) {
return _mm256_min_epu32(_mm256_sub_epi32(broadcast(kP), x), _mm256_sub_epi32(_mm256_setzero_si256(), x));
}
inline Vec even_lanes(Vec x, Vec y) {
const Vec t = _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps(x), _mm256_castsi256_ps(y), 0x88));
return _mm256_permute4x64_epi64(t, 0xD8);
}
inline Vec twice_mod(Vec x) { return reduce(add(x, x), kP); }
inline void transpose(Vec (&r)[8]) {
Vec t[8], u[8];
#pragma GCC unroll 4
for (int i = 0; i < 8; i += 2) t[i] = _mm256_unpacklo_epi32(r[i], r[i + 1]), t[i + 1] = _mm256_unpackhi_epi32(r[i], r[i + 1]);
#pragma GCC unroll 2
for (int i = 0; i < 8; i += 4)
#pragma GCC unroll 2
for (int j = 0; j < 2; ++j)
u[i + 2 * j] = _mm256_unpacklo_epi64(t[i + j], t[i + j + 2]), u[i + 2 * j + 1] = _mm256_unpackhi_epi64(t[i + j], t[i + j + 2]);
#pragma GCC unroll 4
for (int i = 0; i < 4; ++i)
r[i] = _mm256_permute2x128_si256(u[i], u[i + 4], 0x20), r[i + 4] = _mm256_permute2x128_si256(u[i], u[i + 4], 0x31);
}
inline Vec product(Vec x, Vec y) { return _mm256_mul_epu32(x, y); }
inline Vec twice(Vec x) { return _mm256_add_epi64(x, x); }
inline Vec plus(Vec x, Vec y) { return _mm256_add_epi64(x, y); }
inline Vec minus(Vec x, Vec y) { return _mm256_sub_epi64(x, y); }
inline Vec offset() { return _mm256_set1_epi64x(static_cast<long long>(4 * std::uint64_t(kP) * kP)); }
template <size_t N>
[[gnu::always_inline]] inline void odd_lanes(const Vec (&x)[N], Vec (&out)[N]) {
#pragma GCC unroll 8
for (size_t i = 0; i < N; ++i) out[i] = _mm256_srli_epi64(x[i], 32);
}
inline Vec reduce_sums(Vec even, Vec odd) {
return mint_detail::Ops::reduce8(even, odd);
}
[[gnu::always_inline]] inline void graeffe_sums(const Vec (&c)[8], const Vec (&tc)[8], Vec (&sum)[4]) {
const auto x = [&c](int i, int j) { return product(c[i], c[j]); };
const auto t = [&c, &tc](int i, int j) { return product(c[i], tc[j]); };
sum[0] = minus(minus(plus(plus(x(0, 0), twice(t(2, 6))), plus(t(4, 4), offset())), twice(t(1, 7))), twice(t(3, 5)));
sum[1] = minus(minus(plus(plus(twice(x(0, 2)), twice(t(4, 6))), offset()), x(1, 1)), plus(twice(t(3, 7)), t(5, 5)));
sum[2] = minus(minus(plus(plus(twice(x(0, 4)), x(2, 2)), plus(t(6, 6), offset())), twice(x(1, 3))), twice(t(5, 7)));
sum[3] = minus(minus(plus(plus(twice(x(0, 6)), twice(x(2, 4))), offset()), twice(x(1, 5))), plus(x(3, 3), t(7, 7)));
}
[[gnu::always_inline]] inline void graeffe_leaf(const Vec (&c)[8], const Vec (&tc)[8], Vec (&out)[4]) {
Vec even[4], odd[4], c2[8], tc2[8];
graeffe_sums(c, tc, even);
odd_lanes(c, c2), odd_lanes(tc, tc2);
graeffe_sums(c2, tc2, odd);
#pragma GCC unroll 4
for (int k = 0; k < 4; ++k) out[k] = low(reduce_sums(even[k], odd[k]));
}
[[gnu::always_inline]] inline void load_leaves(const Word* from, Vec (&c)[8]) {
#pragma GCC unroll 8
for (int i = 0; i < 8; ++i) c[i] = load(from + 16 * i);
transpose(c);
}
template <bool kNegative>
[[gnu::always_inline]] inline void scaled_leaves(const Vec (&c)[8], const Factors& s, int first, Vec (&tc)[8]) {
#pragma GCC unroll 8
for (int j = 0; j < 8; ++j) {
const Vec x = reduce(times(c[j], s), kP);
tc[j] = j < first ? _mm256_setzero_si256() : kNegative ? negate(x) : x;
}
}
inline void combine_pairs(const Vec (&x)[4], const Vec (&y)[4], const Factors& inverse, Word* out) {
Vec r[8];
#pragma GCC unroll 4
for (int i = 0; i < 4; ++i) r[i] = low(add(x[i], y[i])), r[i + 4] = times(diff(x[i], y[i]), inverse);
transpose(r);
#pragma GCC unroll 8
for (int i = 0; i < 8; ++i) store(out + 8 * i, r[i]);
}
struct Tables {
const Word *roots, *inverse_roots;
static size_t words(size_t m) { return 2 * Arena::footprint(blocked_ntt::detail::table_words(std::countr_zero(m) + 2)); }
Tables(Words memory, size_t m, const Transform* shared = nullptr) {
if (shared && shared->lg_max() >= std::countr_zero(m) + 2) {
roots = shared->roots(), inverse_roots = shared->inverse_roots();
return;
}
auto* const forward = memory.data();
auto* const inverse = forward + Arena::footprint(blocked_ntt::detail::table_words(std::countr_zero(m) + 2));
roots = forward, inverse_roots = inverse;
std::fill(memory.begin(), memory.end(), 0u);
blocked_ntt::detail::build_table(forward, m / 4, blocked_ntt::detail::kRoots[0]);
blocked_ntt::detail::build_table(inverse, m / 4, blocked_ntt::detail::kRoots[1]);
}
};
struct PairWeights {
Factors s, inverse;
PairWeights(const Tables& tables, size_t k) : s(entries(tables.roots, k)), inverse(entries(tables.inverse_roots, k)) {}
};
template <int kParity>
[[gnu::always_inline]] inline void product_sums(const Vec (&a)[8], const Vec (&c)[8], const Vec (&tc)[8],
Vec (&sum)[4]) {
#pragma GCC unroll 4
for (int k = 0; k < 4; ++k) {
Vec s = offset();
#pragma GCC unroll 8
for (int i = 0; i < 8; ++i) {
const int j = 2 * k + kParity - i, l = j + 8;
if (j >= 0) s = j % 2 ? minus(s, product(a[i], c[j])) : plus(s, product(a[i], c[j]));
if (l < 8) s = l % 2 ? minus(s, product(a[i], tc[l])) : plus(s, product(a[i], tc[l]));
}
sum[k] = s;
}
}
template <int kParity>
[[gnu::always_inline]] inline void product_leaf(const Vec (&a)[8], const Vec (&c)[8], const Vec (&tc)[8],
Vec (&out)[4]) {
Vec even[4], odd[4], a2[8], c2[8], tc2[8];
product_sums<kParity>(a, c, tc, even);
odd_lanes(a, a2), odd_lanes(c, c2), odd_lanes(tc, tc2);
product_sums<kParity>(a2, c2, tc2, odd);
#pragma GCC unroll 4
for (int k = 0; k < 4; ++k) out[k] = low(reduce_sums(even[k], odd[k]));
}
template <bool kNumerator>
struct FirstLevelProducts {
static constexpr int kOutputs = kNumerator ? 5 : 3;
const Tables* tables;
const Word *q1, *q2, *p1;
Word* out[kOutputs];
[[gnu::flatten]] void pairs(size_t k) const {
const PairWeights weights(*tables, k);
Vec a[kOutputs][4], b[kOutputs][4];
leaves<false>(16 * k, weights.s, a);
leaves<true>(16 * k + 8, weights.s, b);
#pragma GCC unroll 5
for (int i = 0; i < kOutputs; ++i) combine_pairs(a[i], b[i], weights.inverse, out[i] + 8 * k);
}
template <bool kNegative>
[[gnu::noinline, gnu::flatten]] void leaves(size_t at, const Factors& s, Vec (&r)[kOutputs][4]) const {
Vec c1[8], c2[8], a[8], t1[8], t2[8];
load_leaves(q1 + at, c1), load_leaves(q2 + at, c2);
if constexpr (kNumerator) load_leaves(p1 + at, a);
scaled_leaves<kNegative>(c1, s, kNumerator ? 2 : 4, t1);
scaled_leaves<kNegative>(c2, s, 1, t2);
graeffe_leaf(c1, t1, r[0]);
graeffe_leaf(c2, t2, r[1]);
product_leaf<0>(c1, c2, t2, r[2]);
if constexpr (kNumerator) {
product_leaf<1>(a, c1, t1, r[3]);
product_leaf<1>(a, c2, t2, r[4]);
}
}
};
inline Word graeffe_scale(int lg) {
return blocked_ntt::detail::multiply_mod(kInverseScales[0][lg], Word((std::uint64_t(1) << 31) % kP));
}
struct LevelBottom {
static constexpr bool kForward = true, kInverse = false;
const Tables* tables;
Word* graeffe;
void operator()(Word* a, size_t count, size_t first) const {
ForwardBottom{tables->roots}(a, count, first);
if (graeffe)
for (size_t i = 0; i < count; i += 4) pairs(a + 32 * i, 2 * (first + i));
}
[[gnu::flatten]] void pairs(const Word* q, size_t k) const {
const PairWeights weights(*tables, k);
Vec va[4], vb[4];
leaves<false>(q, weights.s, va);
leaves<true>(q + 8, weights.s, vb);
combine_pairs(va, vb, weights.inverse, graeffe + 8 * k);
}
template <bool kNegative>
[[gnu::noinline, gnu::flatten]] static void leaves(const Word* q, const Factors& s, Vec (&v)[4]) {
Vec c[8], tc[8];
load_leaves(q, c);
scaled_leaves<kNegative>(c, s, 4, tc);
graeffe_leaf(c, tc, v);
}
};
template <class Bottom>
void forward_with(Words a, size_t size, const Tables& tables, const Bottom& bottom) {
const Recursion recursion(tables.roots, tables.inverse_roots, bottom);
const Source in(a.data(), size, 0);
const size_t nv = a.size() / 8;
auto* v = reinterpret_cast<Vec*>(a.data());
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
forward_top4(in, v, h, tables.roots);
for (size_t t = 0; t < 4; ++t) recursion.visit(a.data() + 8 * t * h, h, t);
} else {
const size_t h = nv / 2;
forward_top2(in, v, h);
recursion.visit(a.data(), h, 0);
recursion.visit(a.data() + 8 * h, h, 1);
}
}
template <class Bottom>
void inverse_with(Words a, const Tables& tables, const Bottom& bottom, Word scale, Half output) {
const Recursion recursion(tables.roots, tables.inverse_roots, bottom);
const size_t nv = a.size() / 8;
auto* v = reinterpret_cast<Vec*>(a.data());
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
for (size_t t = 0; t < 4; ++t) recursion.visit(a.data() + 8 * t * h, h, t);
inverse_top4(v, h, output, tables.inverse_roots, Factor(scale));
} else {
const size_t h = nv / 2;
recursion.visit(a.data(), h, 0);
recursion.visit(a.data() + 8 * h, h, 1);
inverse_top2(v, h, output, scale);
}
}
inline void forward_columns(Word* a, size_t h, size_t b, const Word* roots, size_t k) {
Vec* const v = reinterpret_cast<Vec*>(a);
if (b == 0) return kernels::forward_even_columns(v, h, roots + slot(k), roots + slot(2 * k));
kernels::forward_columns(v, h, size_t(1) << b, roots + slot(k), roots + slot(2 * k));
}
inline void inverse_columns(Word* a, size_t h, size_t b, const Word* inverse_roots,
size_t k) {
Vec* const v = reinterpret_cast<Vec*>(a);
if (b == 0) return kernels::inverse_even_columns(v, h, inverse_roots + slot(k), inverse_roots + slot(2 * k));
kernels::inverse_columns(v, h, size_t(1) << b, inverse_roots + slot(k), inverse_roots + slot(2 * k));
}
inline void forward_half(Word* a, size_t h, bool high, const Group& w) {
for (Word* x = a; x < a + 8 * h; x += 8) {
const Vec u = low(load(x));
if (high) {
const Vec b = load(x + 8 * h), yb = times(b, w.y), zb = times(b, w.z);
store(x, add(u, yb)), store(x + 8 * h, diff(u, yb));
store(x + 16 * h, add(u, zb)), store(x + 24 * h, diff(u, zb));
} else {
const Vec xc = times(load(x + 16 * h), w.x), sum = low(add(u, xc)), difference = low_difference(u, xc);
store(x, sum), store(x + 8 * h, sum), store(x + 16 * h, difference), store(x + 24 * h, difference);
}
}
}
inline void inverse_half(Word* a, size_t h, bool high, const Group& w) {
for (Word* x = a; x < a + 8 * h; x += 8) {
const Vec f0 = load(x), f1 = load(x + 8 * h), f2 = load(x + 16 * h), f3 = load(x + 24 * h);
const Vec ab = low(add(f0, f1)), cd = low(add(f2, f3));
store(x, low(add(ab, cd)));
if (high) store(x + 8 * h, low(add(times(diff(f0, f1), w.y), times(diff(f2, f3), w.z))));
else store(x + 16 * h, times(diff(ab, cd), w.x));
}
}
template <class Bottom>
class Pruned {
public:
Pruned(const Tables& tables, const Bottom& bottom, size_t b)
: recursion_(tables.roots, tables.inverse_roots, bottom), tables_(tables), b_(b) {}
void forward(Word* a, size_t nv, size_t k) const {
const size_t h = nv / 4, low = size_t(std::countr_zero(h));
if (nv <= kForwardFull || low + 1 < b_) return recursion_.visit(a, nv, k);
if (low > b_) {
forward_columns(a, h, b_, tables_.roots, k);
for (size_t t = 0; t < 4; ++t) forward(a + 8 * t * h, h, 4 * k + t);
return;
}
forward_half(a, h, low + 1 == b_, Group(tables_.roots, k));
for (size_t t = 0; t < 4; ++t) recursion_.visit(a + 8 * t * h, h, 4 * k + t);
}
void inverse(Word* a, size_t nv, size_t k) const {
const size_t h = nv / 4, low = size_t(std::countr_zero(h));
if (nv <= kInverseFull || low + 1 < b_) return recursion_.visit(a, nv, k);
if (low > b_) {
for (size_t t = 0; t < 4; ++t) inverse(a + 8 * t * h, h, 4 * k + t);
return inverse_columns(a, h, b_, tables_.inverse_roots, k);
}
for (size_t t = 0; t < 4; ++t) recursion_.visit(a + 8 * t * h, h, 4 * k + t);
inverse_half(a, h, low + 1 == b_, Group(tables_.inverse_roots, k));
}
private:
static constexpr size_t kForwardFull = 64, kInverseFull = 16;
Recursion<Bottom> recursion_;
const Tables& tables_;
size_t b_;
};
template <class Bottom>
void forward_pruned(Words a, size_t size, size_t b, const Tables& tables,
const Bottom& bottom) {
const Pruned<Bottom> pruned(tables, bottom, b);
const Source in(a.data(), size, 0);
const size_t nv = a.size() / 8, width = size_t(1) << b;
auto* v = reinterpret_cast<Vec*>(a.data());
const Vec p = broadcast(kP), zero = _mm256_setzero_si256();
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
const Factor z(tables.roots[1], tables.roots[9]);
for (size_t c = 0; c < h; c += 2 * width) {
for (size_t j = c; j < c + width; ++j) {
const Vec x0 = in(j), x1 = in(j + h), x2 = in(j + 2 * h), x3 = in(j + 3 * h);
const Vec ac = add(x0, x2), amc = _mm256_sub_epi32(add(x0, p), x2);
const Vec bd = add(x1, x3), zbmd = times(_mm256_sub_epi32(add(x1, p), x3), z);
v[j] = add(ac, bd), v[j + h] = diff(ac, bd);
v[j + 2 * h] = add(amc, zbmd), v[j + 3 * h] = diff(amc, zbmd);
}
for (size_t j = c + width; j < c + 2 * width; ++j)
v[j] = v[j + h] = v[j + 2 * h] = v[j + 3 * h] = zero;
}
for (size_t t = 0; t < 4; ++t) pruned.forward(a.data() + 8 * t * h, h, t);
} else {
const size_t h = nv / 2;
for (size_t c = 0; c < h; c += 2 * width) {
for (size_t j = c; j < c + width; ++j) {
const Vec x0 = in(j), x1 = in(j + h);
v[j] = add(x0, x1), v[j + h] = _mm256_sub_epi32(add(x0, p), x1);
}
for (size_t j = c + width; j < c + 2 * width; ++j) v[j] = v[j + h] = zero;
}
pruned.forward(a.data(), h, 0);
pruned.forward(a.data() + 8 * h, h, 1);
}
}
template <class Bottom>
void inverse_pruned(Words a, size_t b, const Tables& tables, const Bottom& bottom,
Word scale, Half output = Half::kBoth) {
const Pruned<Bottom> pruned(tables, bottom, b);
const size_t nv = a.size() / 8, width = size_t(1) << b;
auto* v = reinterpret_cast<Vec*>(a.data());
const Factor s(scale);
const auto scaled = [&s](Vec x) __attribute__((target("avx2"))) { return reduce(times(x, s), kP); };
const bool lower = output != Half::kUpper, upper = output != Half::kLower;
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
for (size_t t = 0; t < 4; ++t) pruned.inverse(a.data() + 8 * t * h, h, t);
const Factor z(tables.inverse_roots[1], tables.inverse_roots[9]);
for (size_t c = 0; c < h; c += 2 * width)
for (size_t j = c; j < c + width; ++j) {
const Vec x0 = v[j], x1 = v[j + h], x2 = v[j + 2 * h], x3 = v[j + 3 * h];
const Vec ab = low(add(x0, x1)), cd = low(add(x2, x3));
const Vec amb = low(diff(x0, x1)), cmd = times(diff(x2, x3), z);
if (lower) v[j] = scaled(add(ab, cd)), v[j + h] = scaled(add(amb, cmd));
if (upper) v[j + 2 * h] = scaled(diff(ab, cd)), v[j + 3 * h] = scaled(diff(amb, cmd));
}
} else {
const size_t h = nv / 2;
pruned.inverse(a.data(), h, 0);
pruned.inverse(a.data() + 8 * h, h, 1);
for (size_t c = 0; c < h; c += 2 * width)
for (size_t j = c; j < c + width; ++j) {
const Vec x0 = v[j], x1 = v[j + h];
if (lower) v[j] = scaled(add(x0, x1));
if (upper) v[j + h] = scaled(diff(x0, x1));
}
}
}
class Carve {
public:
explicit Carve(Words scratch) : rest_(scratch) {}
static constexpr size_t words(size_t n) { return Arena::footprint(n) + 8; }
Words take(size_t n) {
const size_t skip = (0 - reinterpret_cast<std::uintptr_t>(rest_.data()) / 4) % 16;
if (skip + Arena::footprint(n) > rest_.size()) std::abort();
const Words s = rest_.subspan(skip, n);
rest_ = rest_.subspan(skip + Arena::footprint(n));
return s;
}
private:
Words rest_;
};
inline void next_level(Words a, size_t stride, size_t rows) {
const size_t half = stride / 2, top = rows * stride;
std::copy(a.begin(), a.begin() + std::ptrdiff_t(half), a.begin() + std::ptrdiff_t(top));
a[top] = a[top] ? a[top] - 1 : kP - 1;
a[0] = 1;
std::fill(a.begin() + 1, a.begin() + std::ptrdiff_t(half), 0);
}
template <size_t K>
void inverse_product_sum(const Tables& tables, const Transform::Pair* pairs, Words out, Half output) {
InverseProductSumBottom<K> bottom{};
for (size_t k = 0; k < K; ++k) bottom.terms[k] = {{tables.roots, tables.inverse_roots, pairs[k].b.data()}, pairs[k].a.data()};
inverse_with(out, tables, bottom, kInverseScales[1][std::countr_zero(out.size())], output);
}
inline void multiply_constant(ConstWords a, Word c, Words t) {
const Factor f(c);
for (size_t i = 0; i < a.size(); i += 8) store(t.data() + i, reduce(times(load(a.data() + i), f), kP));
}
}
inline unsigned coefficient_value(const mint &x) {
const unsigned value = x.v;
const unsigned reduced = (static_cast<unsigned long long>(unsigned(value * 998244351u)) * 998244353u + value) >> 32;
return std::min(reduced, reduced - 998244353u);
}
inline void append_word(std::vector<mint> &out, unsigned value) {
out.emplace_back();
out.back().v = value;
}
}
#pragma GCC pop_options
#endif
#line 5 "components/fps_projection_blocked.hpp"
#if defined(__linux__) && defined(__x86_64__) && defined(__GNUC__) && !defined(__clang__)
#pragma GCC push_options
_Pragma("GCC target(\"avx2\")")
#pragma GCC optimize("O3")
namespace fps_detail::composition_poly {
namespace detail {
inline size_t projection_length(size_t n) { return std::bit_ceil(std::max<size_t>(n, 512)); }
inline void projection_direct(ConstWords g, Words a) {
const size_t n = a.size();
Word power[kComposeBase] = {1}, next[kComposeBase];
for (size_t k = 0; k < n; ++k) {
a[k] = power[n - 1];
for (size_t i = 0; i < n; ++i) {
std::uint64_t c = 0;
for (size_t j = 1; j <= i && j < g.size(); ++j) c += std::uint64_t(g[j]) * power[i - j] % kP;
next[i] = Word(c % kP);
}
std::copy_n(next, n, power);
}
}
inline Vec odd_lanes(Vec x, Vec y) {
const Vec t = _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps(x), _mm256_castsi256_ps(y), 0xDD));
return _mm256_permute4x64_epi64(t, 0xD8);
}
template <bool kAdd, class Op>
void even_terms(ConstWords b, size_t lo, size_t hi, std::ptrdiff_t offset,
Words out, const Op& op) {
const auto scalar = [&op](Word x) {
return Word(_mm_cvtsi128_si32(_mm256_castsi256_si128(op(_mm256_set1_epi32(int(x))))));
};
const auto put = [&out](size_t j, Word x) {
if constexpr (kAdd) out[j] = out[j] + x >= kP ? out[j] + x - kP : out[j] + x;
else out[j] = x;
};
const auto at = [offset](size_t j) { return std::ptrdiff_t(2 * j) + offset; };
const auto first = [&](size_t i) {
const std::ptrdiff_t d = std::ptrdiff_t(i) - offset;
return std::min(out.size(), d <= 0 ? size_t(0) : size_t(d + 1) / 2);
};
const size_t begin = first(lo), end = std::max(begin, first(hi));
if constexpr (!kAdd) {
std::fill(out.begin(), out.begin() + std::ptrdiff_t(begin), 0);
std::fill(out.begin() + std::ptrdiff_t(end), out.end(), 0);
}
size_t j = begin;
for (; j + 8 <= end && size_t(at(j)) + 16 <= b.size(); j += 8) {
const Word* from = b.data() + at(j);
Vec x = op(even_lanes(load_unaligned(from), load_unaligned(from + 8)));
if constexpr (kAdd) x = reduce(add(load_unaligned(out.data() + j), x), kP);
store_unaligned(out.data() + j, x);
}
for (; j < end; ++j) put(j, scalar(b[size_t(at(j))]));
}
inline void add_into(ConstWords x, ConstWords y, Words out) {
for (size_t i = 0; i < out.size(); i += 8) store(out.data() + i, reduce(add(load(x.data() + i), load(y.data() + i)), kP));
}
struct ProjectionBottom {
static constexpr bool kForward = true, kInverse = false;
const Tables* tables;
const Word* p;
Word *v, *w;
void operator()(Word* a, size_t count, size_t first) const {
ForwardBottom{tables->roots}(a, count, first);
for (size_t i = 0; i < count; i += 4) pairs(a + 32 * i, 2 * (first + i));
}
[[gnu::flatten]] void pairs(const Word* q, size_t k) const {
const PairWeights weights(*tables, k);
Vec va[4], vb[4], wa[4], wb[4];
leaves<false>(q, p + 16 * k, weights.s, va, wa);
leaves<true>(q + 8, p + 16 * k + 8, weights.s, vb, wb);
combine_pairs(va, vb, weights.inverse, v + 8 * k);
combine_pairs(wa, wb, weights.inverse, w + 8 * k);
}
template <bool kNegative>
[[gnu::noinline, gnu::flatten]] static void leaves(const Word* q, const Word* p, const Factors& s,
Vec (&v)[4], Vec (&w)[4]) {
Vec c[8], a[8], tc[8];
load_leaves(q, c), load_leaves(p, a);
scaled_leaves<kNegative>(c, s, 2, tc);
graeffe_leaf(c, tc, v);
product_leaf<1>(a, c, tc, w);
}
};
inline void inverse_lower(const Word* a, size_t h, const Group& w, Word* out) {
for (size_t i = 0; i < 8 * h; i += 8) {
const Vec f0 = load(a + i), f1 = load(a + i + 8 * h), f2 = load(a + i + 16 * h), f3 = load(a + i + 24 * h);
store(out + i, low(add(low(add(f0, f1)), low(add(f2, f3)))));
store(out + i + 8 * h, low(add(times(diff(f0, f1), w.y), times(diff(f2, f3), w.z))));
}
}
inline void forward_lower(const Word* in, size_t h, const Group& w, Word* a) {
for (size_t i = 0; i < 8 * h; i += 8) {
const Vec u = low(load(in + i)), b = load(in + i + 8 * h), yb = times(b, w.y), zb = times(b, w.z);
store(a + i, add(u, yb)), store(a + i + 8 * h, diff(u, yb));
store(a + i + 16 * h, add(u, zb)), store(a + i + 24 * h, diff(u, zb));
}
}
template <class Bottom>
class Doubling {
public:
Doubling(Words a, size_t block, const Tables& tables, const Bottom& bottom)
: a_(a.data()), half_(a.size() / 2), block_(block), b_(size_t(std::countr_zero(block)) - 1),
aligned_(std::countr_zero(block) % 2 == 0), tables_(tables), bottom_(bottom),
inverse_(tables.roots, tables.inverse_roots, InverseBottom{tables.inverse_roots, a.data()}),
forward_(tables.roots, tables.inverse_roots, bottom) {}
void run(Word y_scale, Word fix) const {
Word* const copy = a_ + half_;
const size_t nv = half_ / 8, chunk = std::max<size_t>(block_, 16);
for (size_t c = 0; c < nv; c += chunk) {
for (size_t j = c / block_; j < (c + chunk) / block_; ++j) {
inverse_x(a_ + 8 * block_ * j, j, copy + 8 * block_ * j);
forward_x(copy + 8 * block_ * j, j, a_ + 8 * block_ * j);
}
if (block_ < 16) bottom_(a_ + 8 * c, chunk / 4, c / 4);
}
inverse_y(copy, y_scale);
if (fix) {
for (size_t i = 0; i < 4 * block_; i += 8) store(copy + i, negate(load(copy + i)));
copy[0] = fix >= kP - copy[0] ? fix - (kP - copy[0]) : fix + copy[0];
}
forward_upper();
}
private:
void inverse_x(Word* a, size_t j, Word* out) const {
if (block_ == 4) {
inverse_.visit(a, 4, j);
std::copy_n(a, 16, out);
} else if (aligned_) {
const size_t h = block_ / 4;
for (size_t t = 0; t < 4; ++t) inverse_.visit(a + 8 * t * h, h, 4 * j + t);
inverse_lower(a, h, Group(tables_.inverse_roots, j), out);
} else {
const size_t h = block_ / 2;
inverse_.visit(a, h, 2 * j);
inverse_.visit(a + 8 * h, h, 2 * j + 1);
for (size_t i = 0; i < 8 * h; i += 8) store(out + i, low(add(load(a + i), load(a + i + 8 * h))));
}
}
void forward_x(const Word* in, size_t j, Word* a) const {
if (block_ == 4) {
std::copy_n(in, 16, a);
std::fill_n(a + 16, 16, 0);
} else if (aligned_) {
const size_t h = block_ / 4;
forward_lower(in, h, Group(tables_.roots, j), a);
if (h == 4) return bottom_(a, 4, 4 * j);
for (size_t t = 0; t < 4; ++t) forward_.visit(a + 8 * t * h, h, 4 * j + t);
} else {
const size_t h = block_ / 2;
for (size_t i = 0; i < 8 * h; i += 8) {
const Vec x = load(in + i);
store(a + i, x), store(a + i + 8 * h, x);
}
if (h == 4) return;
forward_.visit(a, h, 2 * j);
forward_.visit(a + 8 * h, h, 2 * j + 1);
}
}
void above(Word* a, size_t nv, size_t k) const {
if (nv == block_) return;
if (nv == 2 * block_) {
const Factor x = entry(tables_.inverse_roots, k);
for (size_t i = 0; i < 4 * block_; i += 8) {
const Vec u = load(a + i), v = load(a + i + 8 * block_);
store(a + i, low(add(u, v))), store(a + i + 8 * block_, times(diff(u, v), x));
}
return;
}
const size_t h = nv / 4;
for (size_t t = 0; t < 4; ++t) above(a + 8 * t * h, h, 4 * k + t);
inverse_columns(a, h, b_, tables_.inverse_roots, k);
}
void inverse_y(Word* a, Word scale) const {
const size_t nv = half_ / 8, width = size_t(1) << b_;
const bool zero = b_ <= 5;
Vec* const v = reinterpret_cast<Vec*>(a);
const Factor s(scale);
const auto scaled = [&s](Vec x) __attribute__((target("avx2"))) { return reduce(times(x, s), kP); };
const Vec z0 = _mm256_setzero_si256();
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
for (size_t t = 0; t < 4; ++t) above(a + 8 * t * h, h, t);
const Factor z(tables_.inverse_roots[1], tables_.inverse_roots[9]);
for (size_t c = 0; c < h; c += 2 * width) {
for (size_t j = c; j < c + width; ++j) {
const Vec x0 = v[j], x1 = v[j + h], x2 = v[j + 2 * h], x3 = v[j + 3 * h];
const Vec ab = low(add(x0, x1)), cd = low(add(x2, x3));
const Vec amb = low(diff(x0, x1)), cmd = times(diff(x2, x3), z);
v[j] = scaled(add(ab, cd)), v[j + h] = scaled(add(amb, cmd));
v[j + 2 * h] = scaled(diff(ab, cd)), v[j + 3 * h] = scaled(diff(amb, cmd));
}
if (zero)
for (size_t j = c + width; j < c + 2 * width; ++j) v[j] = v[j + h] = v[j + 2 * h] = v[j + 3 * h] = z0;
}
} else {
const size_t h = nv / 2;
above(a, h, 0);
above(a + 8 * h, h, 1);
for (size_t c = 0; c < h; c += 2 * width) {
for (size_t j = c; j < c + width; ++j) {
const Vec x0 = v[j], x1 = v[j + h];
v[j] = scaled(add(x0, x1)), v[j + h] = scaled(diff(x0, x1));
}
if (zero)
for (size_t j = c + width; j < c + 2 * width; ++j) v[j] = v[j + h] = z0;
}
}
}
void forward_upper() const {
const size_t nv = half_ / 4, width = size_t(1) << b_;
const Pruned<Bottom> pruned(tables_, bottom_, b_);
if (std::countr_zero(nv) % 2 == 0) {
const size_t h = nv / 4;
Vec* const v = reinterpret_cast<Vec*>(a_);
const Factor z(tables_.roots[1], tables_.roots[9]);
for (size_t c = 0; c < h; c += 2 * width)
for (size_t j = c; j < c + width; ++j) {
const Vec x = v[2 * h + j], y = times(v[3 * h + j], z);
v[2 * h + j] = add(x, y), v[3 * h + j] = diff(x, y);
}
pruned.forward(a_ + 16 * h, h, 2);
pruned.forward(a_ + 24 * h, h, 3);
} else {
pruned.forward(a_ + 4 * nv, nv / 2, 1);
}
}
Word* a_;
size_t half_, block_, b_;
bool aligned_;
const Tables& tables_;
Bottom bottom_;
Recursion<InverseBottom> inverse_;
Recursion<Bottom> forward_;
};
inline void projection_first_levels(const Transform& t, const Tables& tables, ConstWords g,
size_t n, Words q, Words p) {
const size_t m = q.size() / 4, h = m / 2, quarter = m / 4, e = m - n, size = std::min(n, g.size());
const auto identity = [&](Vec x) __attribute__((target("avx2"))) { return x; };
const auto negated = [&](Vec x) __attribute__((target("avx2"))) { return negate(x); };
const auto doubled = [&](Vec x) __attribute__((target("avx2"))) { return twice_mod(x); };
const auto minus_twice = [&](Vec x) __attribute__((target("avx2"))) { return negate(twice_mod(x)); };
const auto row = [h, quarter](Words a, size_t k) { return a.subspan(k * h, quarter); };
if (size > 0) {
q[0] = 0;
std::copy(g.begin() + 1, g.begin() + std::ptrdiff_t(size), q.begin() + 1);
}
forward_with(q.first(2 * m), size, tables, LevelBottom{&tables, q.data()});
inverse_with(q.first(m), tables, InverseBottom{tables.inverse_roots, q.data()}, graeffe_scale(std::countr_zero(m)),
Half::kBoth);
const Words q2 = p.subspan(7 * h, h), q1 = q.subspan(7 * h, h), p1 = q.subspan(4 * h, h);
std::copy_n(q.begin(), h, q2.begin());
even_terms<false>(g, 1, size, 0, q1, minus_twice);
if (e % 2) even_terms<false>(g, 1, size, 1 - std::ptrdiff_t(e), p1, negated);
else even_terms<false>(g, 1, size, 1 - std::ptrdiff_t(e), p1, identity);
const Words tq2 = q.first(m), tq1 = q.subspan(m, m), tp1 = p.first(m);
t.forward(tq2.first(h), 0, tq2);
t.forward(q1, 0, tq1);
t.forward(p1, 0, tp1);
Word* const g1 = p.data() + 4 * h;
Word* const g2 = p.data() + 5 * h;
Word* const e12 = p.data() + 6 * h;
Word* const o1 = q.data() + 5 * h;
Word* const o2 = q.data() + 6 * h;
const FirstLevelProducts<true> products{&tables, tq1.data(), tq2.data(), tp1.data(), {g1, g2, e12, o1, o2}};
for (size_t k = 0; k < m / 16; k += 8) products.pairs(k);
const Word scale = graeffe_scale(std::countr_zero(h));
for (Word* out : products.out)
inverse_with(Words(out, h), tables, InverseBottom{tables.inverse_roots, out}, scale, Half::kLower);
std::fill(p.begin(), p.begin() + std::ptrdiff_t(quarter), 0);
if (e % 2 && (e - 1) / 2 % 2) p[(e - 3) / 4] = 1;
even_terms<false>(p1, 0, h, 1, row(p, 1), identity);
std::copy_n(o1, quarter, p.begin() + std::ptrdiff_t(2 * h));
std::copy_n(o2, quarter, p.begin() + std::ptrdiff_t(3 * h));
if (e % 2) {
const size_t d = (e - 1) / 2;
const std::ptrdiff_t offset = 1 - std::ptrdiff_t(d);
const ConstWords b1 = q1, b2 = q2;
if (d % 2) {
even_terms<true>(b1, 0, h, offset, row(p, 1), identity);
even_terms<true>(b2, 0, h, offset, row(p, 2), identity);
} else {
even_terms<true>(b1, 0, h, offset, row(p, 1), negated);
even_terms<true>(b2, 0, h, offset, row(p, 2), negated);
}
}
std::fill(q.begin(), q.begin() + std::ptrdiff_t(quarter), 0);
q[0] = 1;
even_terms<false>(q1, 0, h, 0, row(q, 1), doubled);
even_terms<false>(q2, 0, h, 0, row(q, 2), doubled);
add_into(row(q, 2), ConstWords(g1, quarter), row(q, 2));
for (size_t i = 0; i < quarter; i += 8) store(q.data() + 3 * h + i, twice_mod(load(e12 + i)));
std::copy_n(g2, quarter, q.begin() + std::ptrdiff_t(4 * h));
}
inline void projection_weighted_first(const Transform& t, const Tables& tables,
ConstWords g, ConstWords weights,
Words q, Words p, Words work) {
const size_t m = q.size() / 4, h = m / 2, quarter = m / 4, n = weights.size();
projection_first_levels(t, tables, g, n, q, p);
auto x = work.first(2 * m), y = work.subspan(2 * m, 2 * m), z = work.subspan(4 * m, 2 * m);
std::fill(x.begin(), x.end(), 0u);
std::fill(y.begin(), y.end(), 0u);
for (size_t i = 0; i < n; ++i) x[m - 1 - i] = weights[i];
for (size_t i = 1; i < std::min(g.size(), n); ++i) y[i] = i % 2 && g[i] ? kP - g[i] : g[i];
t.forward(y);
t.cyclic_product(x.first(m), 0, z, y, Half::kLower);
auto p0 = work.first(h), p1 = work.subspan(h, h);
for (size_t i = 0; i < h; ++i) {
const auto value = z[2 * i + 1];
p1[i] = value ? kP - value : 0;
p0[i] = 2 * i + 1 < m && m - 2 - 2 * i < n ? weights[m - 2 - 2 * i] : 0;
}
auto tp0 = work.subspan(m, m), tp1 = work.subspan(2 * m, m);
auto tq1 = work.subspan(3 * m, m), tq2 = work.subspan(4 * m, m), product = work.subspan(5 * m, m);
for (size_t i = 0; i < h; ++i) {
const auto a = q[7 * h + i], b = p[7 * h + i];
tq1[i] = i % 2 && a ? kP - a : a;
tq2[i] = i % 2 && b ? kP - b : b;
}
t.forward(p0, 0, tp0);
t.forward(p1, 0, tp1);
t.forward(tq1.first(h), 0, tq1);
t.forward(tq2.first(h), 0, tq2);
for (size_t i = 0; i < quarter; ++i) p[i] = p0[2 * i + 1];
t.inverse_product(tp0, tq1, product, Half::kLower);
for (size_t i = 0; i < quarter; ++i) {
const auto value = product[2 * i + 1] + p1[2 * i + 1];
p[h + i] = value >= kP ? value - kP : value;
}
const Transform::Pair pairs[2] = {{tp0, tq2}, {tp1, tq1}};
t.inverse_product_sum(pairs, product, Half::kLower);
for (size_t i = 0; i < quarter; ++i) p[2 * h + i] = product[2 * i + 1];
t.inverse_product(tp1, tq2, product, Half::kLower);
for (size_t i = 0; i < quarter; ++i) p[3 * h + i] = product[2 * i + 1];
}
inline size_t projection_work(size_t m) {
const size_t eighth = m / 8;
return std::max(7 * Carve::words(eighth + 1) + 8 * Carve::words(eighth), Carve::words(m) + 2 * Carve::words(m / 2));
}
inline void projection_last_levels(const Transform& t, const Tables& tables, Words q,
Words p, Words w, Words a) {
const size_t m = q.size() / 4, eighth = m / 8, quarter = m / 4, h = m / 2;
Words c[8], pc[8];
{
Carve carve(w);
for (size_t k = 1; k < 8; ++k) c[k] = carve.take(eighth + 1);
for (size_t k = 0; k < 8; ++k) pc[k] = carve.take(eighth);
}
for (size_t r = 0; r < eighth; r += 8) {
Vec x[8];
#pragma GCC unroll 8
for (size_t i = 0; i < 8; ++i) x[i] = load(q.data() + 16 * (r + i));
transpose(x);
#pragma GCC unroll 7
for (size_t k = 1; k < 8; ++k) store(c[k].data() + r, k % 2 ? negate(x[k]) : x[k]);
}
for (size_t k = 1; k < 8; ++k) {
const Word x = q[16 * eighth + k];
c[k][eighth] = k % 2 && x ? kP - x : x;
}
for (size_t r = 0; r < eighth; r += 8) {
Vec x[8];
#pragma GCC unroll 8
for (size_t i = 0; i < 8; ++i) x[i] = load(p.data() + 16 * (r + i));
transpose(x);
#pragma GCC unroll 8
for (size_t k = 0; k < 8; ++k) store(pc[k].data() + r, x[k]);
}
Carve carve_q(q), carve_p(p);
Words tc[8], tp[7], twice[3], cn[4], pn[4];
for (size_t k = 1; k < 8; ++k) {
tc[k] = carve_q.take(quarter);
t.forward(c[k], 0, tc[k]);
}
for (size_t k = 0; k < 7; ++k) {
tp[k] = carve_p.take(quarter);
t.forward(pc[k], 0, tp[k]);
}
for (auto& s : twice) s = carve_q.take(quarter);
multiply_constant(tc[5], 2, twice[0]), multiply_constant(tc[3], kP - 2, twice[1]), multiply_constant(tc[4], kP - 2, twice[2]);
for (size_t k = 1; k < 4; ++k) cn[k] = carve_q.take(quarter + 1);
for (auto& s : pn) s = carve_p.take(quarter);
{
const Transform::Pair first[1] = {{tc[1], tc[1]}}, second[2] = {{tc[2], tc[2]}, {tc[1], twice[1]}},
third[3] = {{tc[3], tc[3]}, {tc[1], twice[0]}, {tc[2], twice[2]}};
t.inverse_product_sum(first, cn[1].first(quarter));
t.inverse_product_sum(second, cn[2].first(quarter));
t.inverse_product_sum(third, cn[3].first(quarter));
}
const Word weight[4] = {0, kP - 2, 2, kP - 2};
for (size_t k = 1; k < 4; ++k) {
const ConstWords linear = c[2 * k];
const Factor f(weight[k]);
for (size_t i = 0; i < eighth; i += 8)
store(cn[k].data() + i, canonical(add(load(cn[k].data() + i), reduce(times(load(linear.data() + i), f), kP))));
const std::uint64_t last = cn[k][eighth] + std::uint64_t(weight[k]) * linear[eighth];
cn[k][eighth] = Word(last % kP);
cn[k][quarter] = cn[k][0];
cn[k][0] = 0;
}
{
Transform::Pair pairs[7];
for (size_t j = 0; j < 4; ++j) {
for (size_t b = 1; b <= 2 * j + 1; ++b) pairs[b - 1] = {tp[2 * j + 1 - b], tc[b]};
const std::span<const Transform::Pair> sum(pairs, 2 * j + 1);
if (j < 2) t.inverse_product_sum(sum, pn[j]);
else if (j == 2) inverse_product_sum<5>(tables, pairs, pn[j], Half::kBoth);
else inverse_product_sum<7>(tables, pairs, pn[j], Half::kBoth);
add_into(pn[j].first(eighth), pc[2 * j + 1], pn[j].first(eighth));
}
}
Carve next_q(q), next_p(p), next_w(w);
Words tcn[4], tpn[3];
for (size_t k = 1; k < 4; ++k) {
tcn[k] = next_q.take(h);
t.forward(cn[k], 0, tcn[k]);
}
for (size_t j = 0; j < 3; ++j) {
tpn[j] = next_p.take(h);
t.forward(pn[j], 0, tpn[j]);
}
const Words square = next_q.take(h), negative_q = next_w.take(m);
t.inverse_product(tcn[1], tcn[1], square);
for (size_t i = 0; i < h; i += 8) {
const Vec x = i < quarter ? negate(twice_mod(load(cn[2].data() + i))) : _mm256_setzero_si256();
store(negative_q.data() + i, reduce(add(x, load(square.data() + i)), kP));
}
{
const Word x = negative_q[quarter] + minus_twice(cn[2][quarter]);
negative_q[quarter] = x >= kP ? x - kP : x;
negative_q[h] = negative_q[0];
negative_q[0] = 0;
std::fill(negative_q.begin() + std::ptrdiff_t(h + 1), negative_q.end(), 0);
}
const Words r1 = next_w.take(h), r3 = next_w.take(h);
t.inverse_product(tpn[0], tcn[1], r1);
add_into(r1.first(quarter), pn[1], r1.first(quarter));
const Transform::Pair pairs[3] = {{tpn[2], tcn[1]}, {tpn[1], tcn[2]}, {tpn[0], tcn[3]}};
t.inverse_product_sum(pairs, r3);
add_into(r3.first(quarter), pn[3], r3.first(quarter));
t.forward(negative_q);
const Words product = next_q.take(m);
t.cyclic_product(r1, 0, product, negative_q);
size_t k = 0;
for (; k + 8 <= std::min(a.size(), h); k += 8)
store_unaligned(a.data() + k, reduce(add(load(r3.data() + k), load(product.data() + k)), kP));
for (; k < a.size(); ++k) {
const Word x = (k < h ? r3[k] : 0) + product[k];
a[k] = x >= kP ? x - kP : x;
}
}
}
inline int projection_log(size_t n) {
if (n <= detail::kComposeBase) return Transform::kMinLog;
return std::countr_zero(detail::projection_length(n));
}
inline size_t projection_scratch(size_t n) {
using detail::Carve;
if (n <= detail::kComposeBase) return 0;
const size_t m = detail::projection_length(n);
return Arena::footprint(detail::Tables::words(m)) + 2 * Carve::words(4 * m) + Carve::words(detail::projection_work(m));
}
namespace detail {
template<bool Weighted>
inline void project(const Transform& t, ConstWords g, ConstWords weights,
Words a, Words scratch) {
const size_t n = a.size();
if constexpr (Weighted) {
assert(n > kComposeBase && weights.size() == n);
} else if (n <= kComposeBase) return projection_direct(g, a);
const size_t m = projection_length(n);
const int lg = std::countr_zero(m);
const Tables tables(scratch.first(Tables::words(m)), m);
Carve carve(scratch.subspan(Arena::footprint(Tables::words(m))));
const Words q = carve.take(4 * m), p = carve.take(4 * m), w = carve.take(projection_work(m));
if constexpr (Weighted) projection_weighted_first(t, tables, g, weights, q, p, scratch.last(6 * m));
else projection_first_levels(t, tables, g, n, q, p);
const ProjectionBottom bottom{&tables, p.data(), q.data(), p.data()};
const Word half_scale = blocked_ntt::detail::power(kP / 2 + 1, 31);
Word c = 1;
for (int s = 2; s + 3 < lg; ++s) {
const size_t stride = 2 * (m >> s), rows = size_t(1) << s;
const size_t pad = size_t(std::countr_zero(stride / 16));
if (s == 2) {
forward_pruned(p, rows * stride, pad, tables, ForwardBottom{tables.roots});
forward_pruned(q, (rows + 1) * stride, pad, tables, bottom);
}
if (s + 4 < lg) {
const size_t block = stride / 16;
const Word next =
blocked_ntt::detail::multiply_mod(blocked_ntt::detail::multiply_mod(blocked_ntt::detail::multiply_mod(c, c), half_scale), Word(block));
const Word y_scale = kInverseScales[0][s + 4];
Doubling<ForwardBottom>(p, block, tables, ForwardBottom{tables.roots}).run(y_scale, 0);
Doubling<ProjectionBottom>(q, block, tables, bottom).run(y_scale, next + next >= kP ? next + next - kP : next + next);
c = next;
} else {
const Word inverse_c = blocked_ntt::detail::power(c, kP - 2);
const Word scale = blocked_ntt::detail::multiply_mod(graeffe_scale(lg + 1), blocked_ntt::detail::multiply_mod(inverse_c, inverse_c));
inverse_pruned(q.first(2 * m), pad - 1, tables, InverseBottom{tables.inverse_roots, q.data()}, scale);
inverse_pruned(p.first(2 * m), pad - 1, tables, InverseBottom{tables.inverse_roots, p.data()}, scale);
next_level(q, stride / 2, 2 * rows);
}
}
projection_last_levels(t, tables, q, p, w, a);
}
}
inline void power_projection(const Transform& t, ConstWords g, Words a,
Words scratch) {
detail::project<false>(t, g, {}, a, scratch);
}
inline void weighted_projection(const Transform& t, ConstWords g,
ConstWords weights, Words a, Words scratch) {
detail::project<true>(t, g, weights, a, scratch);
}
inline void native_weighted_projection(const mint *g, size_t ng, const mint *weights,
std::vector<mint> &out, size_t n, size_t count) {
ng = std::min(ng, n);
const int lg = projection_log(n) + 1;
const size_t scratch_words = projection_scratch(n) + Arena::footprint(6 * detail::projection_length(n));
Arena arena(Transform::words(lg) + Arena::footprint(ng) + 2 * Arena::footprint(n) + Arena::footprint(scratch_words));
const Transform t(arena, lg);
auto a = arena.take(ng), b = arena.take(n), w = arena.take(n);
for (size_t i = 0; i < ng; ++i) a[i] = i ? coefficient_value(g[i]) : 0;
for (size_t i = 0; i < n; ++i) w[i] = std::min(weights[i].v, weights[i].v - mint::mod());
weighted_projection(t, a, w, b, arena.take(scratch_words));
append_fps_words<true>(out, b.data(), count);
}
inline void native_projection(const mint *g, size_t ng, std::vector<mint> &out, size_t n) {
ng = std::min(ng, n);
const int lg = projection_log(n);
Arena arena(Transform::words(lg) + Arena::footprint(ng) + Arena::footprint(n) +
Arena::footprint(projection_scratch(n)));
const Transform t(arena, lg);
auto a = arena.take(ng), b = arena.take(n);
for (size_t i = 0; i < ng; ++i) a[i] = coefficient_value(g[i]);
power_projection(t, a, b, arena.take(projection_scratch(n)));
append_fps_words<false>(out, b.data(), b.size());
}
}
#pragma GCC pop_options
#endif