競技プログラミング用 C++ ライブラリ
convolution/symmetric_group_convolution.hpp · GitHub で見る
共通ページ
長さ \(n\) の全順列を添字とする配列の畳み込みを求めます。通常の加算や XOR の代わりに、順列の合成を用います。
\[c_p=\displaystyle\sum_{q\circ r=p}a_qb_r,\qquad(q\circ r)[i]=q[r[i]].\]順列は 0, ..., n-1 を並べたものとし、配列は 辞書順に並べます。長さは \(n!\) です。合成は交換可能ではなく、一般に convolution(a, b) と convolution(b, a) は異なります。
#include "lib/convolution/symmetric_group_convolution.hpp"
#include <cassert>
std::vector<mint> a{1, 2};
std::vector<mint> b{3, 4};
auto c = symmetric_group_convolution(a, b);
assert((c == std::vector<mint>{11, 10}));
長さ 2 では順列は [0, 1] と [1, 0] の順です。前者は単位元、後者は自分自身との合成で単位元になります。一般の順列と添字の相互変換には 順列の辞書順順位・復元 を使えます。
| 操作 | 引数・返り値 |
|---|---|
symmetric_group_convolution(a, b) |
同じ長さの const std::vector<Mint>& 2 本から std::vector<Mint> を返す。長さから n を決定 |
symmetric_group_convolution(fft, a, b) |
構築済みの const SymmetricGroupFFT<Mint>& を使い、同じ畳み込みを返す |
入力は変更しません。型 Mint は両配列で同じ modint 型とし、法は n より大きい素数を使います。mint・atcoder::modint998244353・atcoder::modint1000000007・動的 modint に対応します。整数からの構築・四則演算・単項マイナス・mod() を備える型を指定してください。動的 modint の法は fft の構築から使用終了まで変更しません。
\(0\le n\le10\) に対応します。空配列、長さが一致しない配列、長さが対応する階乗でない配列には std::invalid_argument を送出します。長さ 1 はスカラーの積を返します。これは空順列の群でも長さ 1 の順列の群でも同じ結果です。構築済み fft を渡す場合は、その size() と配列長を一致させます。
同じ次数で繰り返し計算するときは、変換の準備を共有できます。
#include "lib/convolution/symmetric_group_convolution.hpp"
#include <cassert>
SymmetricGroupFFT<> fft(3);
std::vector<mint> a{1, 2, 3, 4, 5, 6};
std::vector<mint> unit(6);
unit[0] = 1;
assert(symmetric_group_convolution(fft, a, unit) == a);
assert(symmetric_group_convolution(fft, unit, a) == a);
Maslen の対称群 Fourier 変換を使います。部分群への分割を Horner 型にまとめ、Young 図形の経路で中間計算を共有します。有限体上では平方根を必要としない有理的な Young の半正規表現を使います。Fourier 変換した各行列を順に掛け、転置アルゴリズムによる逆変換で係数に戻します。変換自体の API は 対称群 Fourier 変換 を参照してください。
参考: Library Checker issue #1432、David K. Maslen, The efficient computation of Fourier transforms on the symmetric group、同手法の FPSAC 1997 発表版。
\(n\) の分割 \(\lambda\) に対応する表現の次元を \(d_\lambda\) とすると、\(\displaystyle\sum_\lambda d_\lambda^2=n!\) です。畳み込みは前計算後 \(O\left(n^2n!+\displaystyle\sum_\lambda d_\lambda^3\right)\) 時間です。前計算・保持空間は Fourier 変換 と共通で、計算時の追加作業空間は \(O(n!)\) です。体の演算を \(O(1)\) とします。
#ifndef SYMMETRIC_GROUP_CONVOLUTION_HPP
#define SYMMETRIC_GROUP_CONVOLUTION_HPP
#include "../components/symmetric_group_fft.hpp"
#include "../components/matrix_arithmetic.hpp"
#include <stdexcept>
#include <vector>
template <class Mint>
std::vector<Mint> symmetric_group_convolution(const SymmetricGroupFFT<Mint> &fft, const std::vector<Mint> &a, const std::vector<Mint> &b) {
auto x = fft.forward(a), y = fft.forward(b);
for (const auto &block : fft.blocks()) {
int d = block.dimension, offset = block.offset, length = d * d;
std::vector<Mint> left(x.begin() + offset, x.begin() + offset + length);
std::vector<Mint> right(y.begin() + offset, y.begin() + offset + length);
auto product = matrix_detail::multiply(left, right, d, d, d);
std::copy(product.begin(), product.end(), x.begin() + offset);
}
return fft.inverse(x);
}
template <class Mint>
std::vector<Mint> symmetric_group_convolution(const std::vector<Mint> &a, const std::vector<Mint> &b) {
int n = 0, size = 1;
while (std::size_t(size) < a.size() && n < 10) size *= ++n;
if (a.size() != std::size_t(size) || b.size() != a.size()) throw std::invalid_argument("symmetric_group_convolution size");
return symmetric_group_convolution(SymmetricGroupFFT<Mint>(n), a, b);
}
#endif
#line 1 "convolution/symmetric_group_convolution.hpp"
#line 1 "components/symmetric_group_fft.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/symmetric_group_fft.hpp"
#include <algorithm>
#include <bit>
#line 8 "components/symmetric_group_fft.hpp"
#include <cstdint>
#include <map>
#include <numeric>
#include <span>
#include <stdexcept>
#include <tuple>
#include <utility>
#include <vector>
template <class Mint = mint>
class SymmetricGroupFFT {
public:
struct Block {
std::vector<int> partition;
int dimension = 0, offset = 0;
};
private:
struct Shape {
Block block;
int first = 0;
std::map<int, int> branches;
};
struct Tableau {
std::uint64_t code;
int shape, parent, content;
};
struct Level {
std::vector<Shape> shapes;
std::vector<Tableau> tableaux;
std::vector<std::vector<int>> children;
std::map<std::vector<int>, int> shapes_by_parts;
std::map<std::uint64_t, int> tableaux_by_code;
};
struct Copy { int from, to, width; };
struct Term { int from, to, width; Mint coefficient; };
struct Step {
std::vector<Copy> copies;
std::vector<Term> terms;
};
struct Row { int shape; std::uint64_t suffix; int beta, cut, offset; };
int n;
std::uint64_t modulus;
std::vector<int> factorial, order, inverse_order;
std::vector<Block> block_data;
std::vector<std::vector<Step>> plans;
static void partitions(int left, int maximum, std::vector<int> &parts, std::vector<std::vector<int>> &out) {
if (!left) { out.push_back(parts); return; }
for (int x = std::min(left, maximum); x; --x) {
parts.push_back(x);
partitions(left - x, x, parts, out);
parts.pop_back();
}
}
void build_plans() {
std::vector<Mint> reciprocals(2 * n + 1);
for (int i = 1; i <= n; ++i) {
reciprocals[n + i] = Mint(1) / Mint(i);
reciprocals[n - i] = -reciprocals[n + i];
}
std::vector<Level> levels(n + 1);
for (int k = 0; k <= n; ++k) {
auto &level = levels[k];
std::vector<std::vector<int>> parts;
std::vector<int> path;
partitions(k, k, path, parts);
int offset = 0;
for (const auto &p : parts) {
int id = int(level.shapes.size());
Shape shape;
shape.block.partition = p;
shape.first = int(level.tableaux.size());
if (!k) level.tableaux.push_back({0, id, 0, 0});
for (int r = 0; r < int(p.size()); ++r) {
if (r + 1 < int(p.size()) && p[r] == p[r + 1]) continue;
auto previous = p;
if (!--previous[r]) previous.pop_back();
const auto &lower = levels[k - 1];
int mu = lower.shapes_by_parts.at(previous);
shape.branches.emplace(mu, int(level.tableaux.size()) - shape.first);
const auto &parent = lower.shapes[mu];
for (int j = 0; j < parent.block.dimension; ++j) {
int t = parent.first + j;
level.tableaux.push_back({lower.tableaux[t].code | (std::uint64_t(r) << (4 * (k - 1))), id, t, p[r] - 1 - r});
}
}
shape.block.dimension = int(level.tableaux.size()) - shape.first;
shape.block.offset = offset;
offset += shape.block.dimension * shape.block.dimension;
level.shapes_by_parts.emplace(p, id);
level.shapes.push_back(std::move(shape));
}
assert(offset == factorial[k]);
if (k) levels[k - 1].children.resize(levels[k - 1].tableaux.size());
for (int t = 0; t < int(level.tableaux.size()); ++t) {
level.tableaux_by_code.emplace(level.tableaux[t].code, t);
if (k) levels[k - 1].children[level.tableaux[t].parent].push_back(t);
}
}
for (const auto &shape : levels[n].shapes) block_data.push_back(shape.block);
for (int m = 2; m <= n; ++m) {
const auto &previous = levels[m - 1];
std::vector<Row> rows;
for (int mu = 0; mu < int(previous.shapes.size()); ++mu) {
const auto &s = previous.shapes[mu];
for (int j = 0; j < s.block.dimension; ++j)
rows.push_back({mu, previous.tableaux[s.first + j].code, 0, 0, s.block.offset + j * s.block.dimension});
}
for (int i = 2; i <= m; ++i) {
Step step;
std::vector<Row> next;
std::map<std::tuple<int, std::uint64_t, int>, int> ids;
int size = 0;
for (const auto &row : rows) {
auto a = levels[i - 2].shapes[row.cut].block.partition;
int r = int(row.suffix & 15);
if (r == int(a.size())) a.push_back(1);
else ++a[r];
int cut = levels[i - 1].shapes_by_parts.at(a);
int bp = levels[i - 1].tableaux[row.beta].shape;
int width = previous.shapes[row.shape].block.dimension;
for (int beta : levels[i - 1].children[row.beta]) {
const auto &tableau = levels[i].tableaux[beta];
const auto &shape = levels[i].shapes[tableau.shape];
if (!shape.branches.contains(cut)) continue;
auto key = std::tuple(row.shape, row.suffix >> 4, beta);
auto [it, added] = ids.emplace(key, int(next.size()));
if (added) {
int offset = i == m ? shape.block.offset + (beta - shape.first) * shape.block.dimension + shape.branches.at(row.shape) : size;
next.push_back({row.shape, row.suffix >> 4, beta, cut, offset});
size += width;
}
const auto &before = levels[i - 2].shapes[row.cut].block.partition;
const auto &after = levels[i - 1].shapes[bp].block.partition;
int corner = 0;
while (corner < int(before.size()) && before[corner] == after[corner]) ++corner;
int d = tableau.content - (after[corner] - 1 - corner);
assert(d && -n <= d && d <= n);
Mint coefficient = reciprocals[n + d] + Mint(bp != cut);
step.terms.push_back({row.offset, next[it->second].offset, width, coefficient});
}
}
assert(size == i * factorial[m - 1]);
for (const auto &row : next) {
const auto &parent = levels[i - 1].tableaux[levels[i].tableaux[row.beta].parent];
if (parent.shape != row.cut) continue;
std::uint64_t code = (row.suffix << (4 * (i - 1))) | parent.code;
int t = previous.tableaux_by_code.at(code);
const auto &shape = previous.shapes[row.shape];
step.copies.push_back({shape.block.offset + (t - shape.first) * shape.block.dimension, row.offset, shape.block.dimension});
}
plans[m].push_back(std::move(step));
rows.swap(next);
}
}
}
int rank(const std::vector<int> &p, bool reversed) const {
unsigned used = 0;
int result = 0;
for (int i = 0; i < n; ++i) {
int x = p[reversed ? n - 1 - i : i];
result = result * (n - i) + x - std::popcount(used & ((1u << x) - 1));
used |= 1u << x;
}
return result;
}
void transform(int m, Mint *a, std::vector<Mint> &x, std::vector<Mint> &y, bool transpose) const {
if (m <= 1) return;
int sub = factorial[m - 1];
if (!transpose) {
for (int i = 0; i < m; ++i) transform(m - 1, a + i * sub, x, y, false);
x.assign(a, a + sub);
for (int i = 2; i <= m; ++i) {
const auto &step = plans[m][i - 2];
y.assign(i * sub, Mint(0));
for (auto copy : step.copies) std::copy_n(a + (i - 1) * sub + copy.from, copy.width, y.data() + copy.to);
for (const auto &term : step.terms)
for (int j = 0; j < term.width; ++j) y[term.to + j] += term.coefficient * x[term.from + j];
x.swap(y);
}
std::copy(x.begin(), x.end(), a);
} else {
x.assign(a, a + factorial[m]);
std::fill_n(a, factorial[m], Mint(0));
for (int i = m; i >= 2; --i) {
const auto &step = plans[m][i - 2];
y.assign((i - 1) * sub, Mint(0));
for (auto copy : step.copies)
for (int j = 0; j < copy.width; ++j) a[(i - 1) * sub + copy.from + j] += x[copy.to + j];
for (const auto &term : step.terms)
for (int j = 0; j < term.width; ++j) y[term.from + j] += term.coefficient * x[term.to + j];
x.swap(y);
}
std::copy(x.begin(), x.end(), a);
for (int i = 0; i < m; ++i) transform(m - 1, a + i * sub, x, y, true);
}
}
void check(std::size_t size) const {
assert(modulus == std::uint64_t(Mint::mod()));
if (size != std::size_t(factorial[n])) throw std::invalid_argument("SymmetricGroupFFT size");
}
public:
explicit SymmetricGroupFFT(int degree = 0) : n(degree), modulus(Mint::mod()) {
if (n < 0 || n > 10 || modulus <= unsigned(n)) throw std::invalid_argument("SymmetricGroupFFT degree or modulus");
factorial.assign(n + 1, 1);
for (int i = 1; i <= n; ++i) factorial[i] = factorial[i - 1] * i;
plans.resize(n + 1);
build_plans();
order.resize(factorial[n]);
inverse_order.resize(factorial[n]);
std::vector<int> p(n), inverse(n);
std::iota(p.begin(), p.end(), 0);
int index = 0;
do {
int internal = rank(p, true);
order[index++] = internal;
for (int i = 0; i < n; ++i) inverse[p[i]] = i;
inverse_order[internal] = rank(inverse, false);
} while (std::next_permutation(p.begin(), p.end()));
}
int degree() const { return n; }
int size() const { return factorial[n]; }
const std::vector<Block> &blocks() const { return block_data; }
std::vector<Mint> forward(std::span<const Mint> a) const {
check(a.size());
std::vector<Mint> result(a.size()), x, y;
for (int i = 0; i < size(); ++i) result[order[i]] = a[i];
transform(n, result.data(), x, y, false);
return result;
}
std::vector<Mint> inverse(std::span<const Mint> a) const {
check(a.size());
std::vector<Mint> result(a.size()), work(a.size()), x, y;
Mint scale = Mint(1) / Mint(size());
for (const auto &block : block_data) {
int d = block.dimension, offset = block.offset;
Mint weight = scale * Mint(d);
for (int i = 0; i < d; ++i) for (int j = 0; j < d; ++j) work[offset + i * d + j] = a[offset + j * d + i] * weight;
}
transform(n, work.data(), x, y, true);
for (int i = 0; i < size(); ++i) result[inverse_order[i]] = work[i];
return result;
}
};
#line 1 "components/matrix_arithmetic.hpp"
#line 1 "components/matrix_base_arithmetic.hpp"
#line 1 "components/field_base.hpp"
#include <cmath>
#line 6 "components/field_base.hpp"
namespace field_detail {
template <class T>
struct Field {
using V = T;
static T add(T a, T b) { return a + b; }
static T mul(T a, T b) { return a * b; }
static T neg(T a) { return -a; }
static T inv(T a) { return T(1) / a; }
static bool zero(T a, long double eps) {
if constexpr (std::is_floating_point_v<T>) return std::abs(a) <= eps;
else { (void)eps; return a == T(0); }
}
static bool better(T a, T b) {
if constexpr (std::is_floating_point_v<T>) return std::abs(a) > std::abs(b);
else { (void)a; (void)b; return false; }
}
static T dot(const T *a, const T *b, int n) {
T result = 0;
for (int i = 0; i < n; ++i) result += a[i] * b[i];
return result;
}
static void scale(T *a, int n, T x) { for (int i = 0; i < n; ++i) a[i] *= x; }
static void axpy(T *a, const T *b, int n, T x) { for (int i = 0; i < n; ++i) a[i] += x * b[i]; }
};
}
#line 7 "components/matrix_base_arithmetic.hpp"
#include <cstddef>
#line 9 "components/matrix_base_arithmetic.hpp"
#include <limits>
#line 13 "components/matrix_base_arithmetic.hpp"
namespace matrix_detail {
template <class T>
inline constexpr bool integer = std::is_integral_v<T> || std::is_same_v<T, __int128_t> || std::is_same_v<T, __uint128_t>;
using field_detail::Field;
template <class Ops>
struct Elimination {
std::vector<int> pivots;
typename Ops::V determinant = 1;
};
template <class Ops, bool exact = false>
Elimination<Ops> eliminate(std::vector<typename Ops::V> &a, int h, int stride, int columns, bool reduced, long double eps) {
using V = typename Ops::V;
Elimination<Ops> result;
result.pivots.reserve(std::min(h, columns));
int rank = 0;
for (int col = 0; col < columns && rank < h; ++col) {
int p = -1;
for (int i = rank; i < h; ++i) {
V x = a[std::size_t(i) * stride + col];
if (!Ops::zero(x, eps) && (p < 0 || Ops::better(x, a[std::size_t(p) * stride + col]))) p = i;
}
if (p < 0) continue;
V *row = a.data() + std::size_t(rank) * stride;
if (p != rank) {
std::swap_ranges(row, row + stride, a.data() + std::size_t(p) * stride);
result.determinant = Ops::neg(result.determinant);
}
result.determinant = Ops::mul(result.determinant, row[col]);
Ops::scale(row + col + 1, stride - col - 1, Ops::inv(row[col]));
row[col] = V(1);
for (int i = rank + 1; i < h; ++i) {
V *dst = a.data() + std::size_t(i) * stride;
V x = dst[col];
if (!Ops::zero(x, eps)) Ops::axpy(dst + col + 1, row + col + 1, stride - col - 1, Ops::neg(x));
dst[col] = V(0);
}
result.pivots.push_back(col);
++rank;
}
bool full = exact && rank == columns;
if (reduced) for (int k = rank - 1; k >= 0; --k) {
int col = result.pivots[k], start = full ? columns : col + 1;
const V *row = a.data() + std::size_t(k) * stride;
for (int i = 0; i < k; ++i) {
V *dst = a.data() + std::size_t(i) * stride;
V x = dst[col];
if (!Ops::zero(x, eps) && (!full || start < stride)) Ops::axpy(dst + start, row + start, stride - start, Ops::neg(x));
dst[col] = V(0);
}
}
return result;
}
template <class T>
[[gnu::noinline]] T integer_determinant(const std::vector<T> &a, int n) {
static_assert(std::numeric_limits<T>::is_signed);
using W = __int128_t;
std::vector<W> work(a.begin(), a.end());
W previous = 1, sign = 1;
if (!n) return T(1);
for (int k = 0; k + 1 < n; ++k) {
int pivot = k;
while (pivot < n && work[std::size_t(pivot) * n + k] == 0) ++pivot;
if (pivot == n) return T(0);
if (pivot != k) { for (int j = 0; j < n; ++j) std::swap(work[std::size_t(k) * n + j], work[std::size_t(pivot) * n + j]); sign = -sign; }
W p = work[std::size_t(k) * n + k];
for (int i = k + 1; i < n; ++i) {
for (int j = k + 1; j < n; ++j) {
W value = work[std::size_t(i) * n + j] * p - work[std::size_t(i) * n + k] * work[std::size_t(k) * n + j];
assert(value % previous == 0);
work[std::size_t(i) * n + j] = value / previous;
}
work[std::size_t(i) * n + k] = 0;
}
previous = p;
}
W result = sign * work.back();
assert(result >= std::numeric_limits<T>::lowest() && result <= std::numeric_limits<T>::max());
return T(result);
}
template <class T>
std::vector<T> basic_multiply(const std::vector<T> &a, const std::vector<T> &b, int n, int inner, int m) {
std::vector<T> c(std::size_t(n) * m, T(0));
for (int i = 0; i < n; ++i) for (int k = 0; k < inner; ++k) {
const T &x = a[std::size_t(i) * inner + k];
for (int j = 0; j < m; ++j) c[std::size_t(i) * m + j] += x * b[std::size_t(k) * m + j];
}
return c;
}
}
#line 1 "components/field_arithmetic.hpp"
#line 9 "components/field_arithmetic.hpp"
#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
#include <immintrin.h>
#endif
namespace field_detail {
template <class T, class = void>
struct SmallModint : std::false_type {};
template <class T>
struct SmallModint<T, std::void_t<decltype(std::integral_constant<std::uint32_t, T::mod()>{}),
decltype(std::declval<T>().val()), decltype(T::raw(std::uint32_t{}))>>
: std::bool_constant<(T::mod() > 1 && T::mod() < (std::uint32_t(1) << 31))
&& std::is_convertible_v<decltype(std::declval<T>().val()), std::uint32_t>
&& std::is_same_v<decltype(T::raw(std::uint32_t{})), T>> {};
template <class T>
inline constexpr bool small_modint = SmallModint<T>::value;
inline bool avx2() {
#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__)) && !defined(FIELD_DISABLE_AVX2) && !defined(MATRIX_DISABLE_AVX2)
static const bool enabled = __builtin_cpu_supports("avx2");
return enabled;
#else
return false;
#endif
}
template <class T>
struct ModularField {
using V = std::uint32_t;
static constexpr V mod = T::mod();
static V add(V a, V b) { V c = a + b; return c < mod ? c : c - mod; }
static V mul(V a, V b) { return std::uint64_t(a) * b % mod; }
static V neg(V a) { return a ? mod - a : 0; }
static V dot(const V *a, const V *b, int n) {
constexpr int step = mod <= (V(1) << 30) ? 16 : 4;
std::uint64_t sum = 0;
for (int i = 0; i < n; i += step) {
for (int j = i; j < std::min(n, i + step); ++j) sum += std::uint64_t(a[j]) * b[j];
sum %= mod;
}
return sum;
}
static V inv(V a) { return (T(1) / T::raw(a)).val(); }
static bool zero(V a, long double) { return !a; }
static bool better(V, V) { return false; }
static V product(V a, V x, V q) {
V r = a * x - V((std::uint64_t(a) * q) >> 32) * mod;
return r < mod ? r : r - mod;
}
#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
__attribute__((target("avx2"))) static __m256i product8(__m256i a, __m256i x, __m256i q) {
__m256i p = _mm256_set1_epi32(mod);
__m256i even = _mm256_srli_epi64(_mm256_mul_epu32(a, q), 32);
__m256i odd = _mm256_mul_epu32(_mm256_srli_epi64(a, 32), q);
__m256i high = _mm256_or_si256(even, _mm256_slli_epi64(_mm256_srli_epi64(odd, 32), 32));
__m256i r = _mm256_sub_epi32(_mm256_mullo_epi32(a, x), _mm256_mullo_epi32(high, p));
return _mm256_min_epu32(r, _mm256_sub_epi32(r, p));
}
__attribute__((target("avx512f"))) static __m512i product16(__m512i a, __m512i x, __m512i q) {
const auto p = _mm512_set1_epi32(mod);
auto even = _mm512_srli_epi64(_mm512_mul_epu32(a, q), 32);
auto odd = _mm512_mul_epu32(_mm512_srli_epi64(a, 32), q);
auto high = _mm512_mask_blend_epi32(0xaaaa, even, odd);
auto r = _mm512_sub_epi32(_mm512_mullo_epi32(a, x), _mm512_mullo_epi32(high, p));
return _mm512_min_epu32(r, _mm512_sub_epi32(r, p));
}
template <bool add_to>
__attribute__((target("avx512f"))) static void apply16(V *a, const V *b, int n, V x, V q) {
const auto vx = _mm512_set1_epi32(x), vq = _mm512_set1_epi32(q), p = _mm512_set1_epi32(mod);
int i = 0;
for (; i + 16 <= n; i += 16) {
auto r = product16(_mm512_loadu_si512(b + i), vx, vq);
if constexpr (add_to) {
r = _mm512_add_epi32(r, _mm512_loadu_si512(a + i));
r = _mm512_min_epu32(r, _mm512_sub_epi32(r, p));
}
_mm512_storeu_si512(a + i, r);
}
for (; i < n; ++i) {
V r = product(b[i], x, q);
if constexpr (add_to) a[i] = add(a[i], r);
else a[i] = r;
}
}
template <bool add_to>
__attribute__((target("avx2"))) static void apply8(V *a, const V *b, int n, V x, V q) {
__m256i vx = _mm256_set1_epi32(x), vq = _mm256_set1_epi32(q), p = _mm256_set1_epi32(mod);
int i = 0;
for (; i + 8 <= n; i += 8) {
__m256i r = product8(_mm256_loadu_si256(reinterpret_cast<const __m256i *>(b + i)), vx, vq);
if constexpr (add_to) {
r = _mm256_add_epi32(r, _mm256_loadu_si256(reinterpret_cast<const __m256i *>(a + i)));
r = _mm256_min_epu32(r, _mm256_sub_epi32(r, p));
}
_mm256_storeu_si256(reinterpret_cast<__m256i *>(a + i), r);
}
for (; i < n; ++i) {
V r = product(b[i], x, q);
if constexpr (add_to) a[i] = add(a[i], r);
else a[i] = r;
}
}
#endif
template <bool add_to>
static void apply(V *a, const V *b, int n, V x) {
V q = (std::uint64_t(x) << 32) / mod;
#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
if (n >= 64 && avx2() && __builtin_cpu_supports("avx512f")) { apply16<add_to>(a, b, n, x, q); return; }
if (n >= 16 && avx2()) { apply8<add_to>(a, b, n, x, q); return; }
#endif
for (int i = 0; i < n; ++i) {
V r = product(b[i], x, q);
if constexpr (add_to) a[i] = add(a[i], r);
else a[i] = r;
}
}
static void scale(V *a, int n, V x) { apply<false>(a, a, n, x); }
static void axpy(V *a, const V *b, int n, V x) { apply<true>(a, b, n, x); }
};
}
#line 6 "components/matrix_arithmetic.hpp"
#line 15 "components/matrix_arithmetic.hpp"
#if defined(__x86_64__) || defined(__i386__)
#include <immintrin.h>
#endif
namespace matrix_detail {
using field_detail::SmallModint, field_detail::small_modint, field_detail::avx2, field_detail::ModularField;
template <class T, class F>
auto field(std::vector<T> &a, F &&f) {
static_assert(!integer<T>);
if constexpr (small_modint<T>) {
std::vector<std::uint32_t> work(a.size());
for (std::size_t i = 0; i < a.size(); ++i) work[i] = a[i].val();
auto result = f.template operator()<ModularField<T>>(work);
for (std::size_t i = 0; i < a.size(); ++i) a[i] = T::raw(work[i]);
return result;
} else return f.template operator()<Field<T>>(a);
}
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC push_options
#pragma GCC optimize("O3")
#endif
template <std::uint32_t mod>
void product_scalar(const std::uint32_t *a, const std::uint32_t *b, std::uint32_t *c, int n, int inner, int m) {
constexpr int step = mod <= (std::uint32_t(1) << 30) ? 16 : 4;
for (int i = 0; i < n; ++i) for (int j = 0; j < m; j += 32) {
int width = std::min(32, m - j);
for (int k = 0; k < inner; k += step) {
std::uint64_t sums[32]{};
for (int t = k; t < std::min(inner, k + step); ++t) {
std::uint64_t x = a[std::size_t(i) * inner + t];
const auto *row = b + std::size_t(t) * m + j;
for (int s = 0; s < width; ++s) sums[s] += x * row[s];
}
for (int s = 0; s < width; ++s) c[std::size_t(i) * m + j + s] = (sums[s] + c[std::size_t(i) * m + j + s]) % mod;
}
}
}
#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
inline bool avx512_product() {
static const bool supported = __builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512dq");
return avx2() && supported;
}
template <std::uint32_t mod>
__attribute__((target("avx512f,avx512dq"))) __m512i reduce_product_avx512(__m512i sum) {
const auto p = _mm512_set1_epi64(mod);
const auto q = _mm512_cvt_roundpd_epi64(
_mm512_mul_pd(_mm512_cvtepi64_pd(sum), _mm512_set1_pd(1.0 / mod)),
_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC);
auto r = _mm512_sub_epi64(sum, _mm512_mullo_epi64(q, p));
r = _mm512_mask_add_epi64(r, _mm512_cmp_epi64_mask(r, _mm512_setzero_si512(), _MM_CMPINT_LT), r, p);
return _mm512_mask_sub_epi64(r, _mm512_cmp_epi64_mask(r, p, _MM_CMPINT_NLT), r, p);
}
template <std::uint32_t mod, int rows = 8>
__attribute__((target("avx512f,avx512dq"))) void product_packed_avx512(const std::int32_t *a, const std::int32_t *b, std::uint32_t *c, int n, int inner, int m) {
constexpr std::int64_t radius = mod / 2;
constexpr std::int64_t residue = (std::uint64_t(1) << 32) % mod;
constexpr std::int64_t folded_bound = (std::int64_t(1) << 31) * residue + 0xffffffffu;
constexpr int step = int(std::min<std::int64_t>(32, (std::numeric_limits<std::int64_t>::max() - folded_bound) / (radius * radius)));
int i = 0;
for (; i + rows <= n; i += rows) {
for (int j = 0; j < m; j += 16) {
const int width = std::min(16, m - j);
const __mmask16 active = (1u << width) - 1;
__m512i sums[rows][2];
for (int r = 0; r < rows; ++r) {
auto previous = _mm512_maskz_loadu_epi32(active, c + std::size_t(i + r) * m + j);
sums[r][0] = _mm512_and_si512(previous, _mm512_set1_epi64(0xffffffffu));
sums[r][1] = _mm512_srli_epi64(previous, 32);
}
for (int k = 0; k < inner; k += step) {
for (int t = k; t < std::min(inner, k + step); ++t) {
auto x = _mm512_maskz_loadu_epi32(active, b + std::size_t(j) * inner + std::size_t(t) * width);
auto y = _mm512_srli_epi64(x, 32);
for (int r = 0; r < rows; ++r) {
auto z = _mm512_set1_epi32(a[std::size_t(i + r) * inner + t]);
sums[r][0] = _mm512_add_epi64(sums[r][0], _mm512_mul_epi32(x, z));
sums[r][1] = _mm512_add_epi64(sums[r][1], _mm512_mul_epi32(y, z));
}
}
for (int r = 0; r < rows; ++r) for (int q = 0; q < 2; ++q) {
auto x = sums[r][q];
sums[r][q] = _mm512_add_epi64(_mm512_mul_epi32(_mm512_srli_epi64(x, 32), _mm512_set1_epi32(residue)), _mm512_and_si512(x, _mm512_set1_epi64(0xffffffffu)));
}
}
for (int r = 0; r < rows; ++r) {
auto even = reduce_product_avx512<mod>(sums[r][0]), odd = reduce_product_avx512<mod>(sums[r][1]);
_mm512_mask_storeu_epi32(c + std::size_t(i + r) * m + j, active, _mm512_or_si512(even, _mm512_slli_epi64(odd, 32)));
}
}
}
if constexpr (rows > 1) {
if (i < n) product_packed_avx512<mod, rows / 2>(a + std::size_t(i) * inner, b, c + std::size_t(i) * m, n - i, inner, m);
}
}
template <std::uint32_t mod, int rows = 4>
__attribute__((target("avx2"))) void product_packed_avx2(const std::int32_t *a, const std::int32_t *b, std::uint32_t *c, int n, int inner, int m) {
constexpr std::int64_t radius = mod / 2;
constexpr std::int64_t residue = (std::uint64_t(1) << 32) % mod;
constexpr std::int64_t folded_bound = (std::int64_t(1) << 31) * residue + 0xffffffffu;
constexpr int step = int(std::min<std::int64_t>(32, (std::numeric_limits<std::int64_t>::max() - folded_bound) / (radius * radius)));
auto reduce = [](std::int64_t sum) {
std::int64_t r = sum % std::int64_t(mod);
return std::uint32_t(r < 0 ? r + mod : r);
};
int i = 0;
for (; i + rows <= n; i += rows) {
for (int j = 0; j < m; j += 8) {
int width = std::min(8, m - j);
auto active = _mm256_cmpgt_epi32(_mm256_set1_epi32(width), _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7));
__m256i sums[rows][2];
for (int r = 0; r < rows; ++r) {
auto previous = _mm256_maskload_epi32(reinterpret_cast<const int *>(c + std::size_t(i + r) * m + j), active);
sums[r][0] = _mm256_and_si256(previous, _mm256_set1_epi64x(0xffffffffu));
sums[r][1] = _mm256_srli_epi64(previous, 32);
}
for (int k = 0; k < inner; k += step) {
for (int t = k; t < std::min(inner, k + step); ++t) {
auto x = _mm256_maskload_epi32(b + std::size_t(j) * inner + std::size_t(t) * width, active);
auto y = _mm256_srli_epi64(x, 32);
for (int r = 0; r < rows; ++r) {
auto z = _mm256_set1_epi32(a[std::size_t(i + r) * inner + t]);
sums[r][0] = _mm256_add_epi64(sums[r][0], _mm256_mul_epi32(x, z));
sums[r][1] = _mm256_add_epi64(sums[r][1], _mm256_mul_epi32(y, z));
}
}
for (int r = 0; r < rows; ++r) for (int q = 0; q < 2; ++q) {
auto x = sums[r][q];
sums[r][q] = _mm256_add_epi64(_mm256_mul_epi32(_mm256_srli_epi64(x, 32), _mm256_set1_epi32(residue)), _mm256_and_si256(x, _mm256_set1_epi64x(0xffffffffu)));
}
}
for (int r = 0; r < rows; ++r) {
std::int64_t even[4], odd[4];
_mm256_storeu_si256(reinterpret_cast<__m256i *>(even), sums[r][0]);
_mm256_storeu_si256(reinterpret_cast<__m256i *>(odd), sums[r][1]);
std::uint32_t result[8];
for (int q = 0; q < 4; ++q) result[q * 2] = reduce(even[q]), result[q * 2 + 1] = reduce(odd[q]);
_mm256_maskstore_epi32(reinterpret_cast<int *>(c + std::size_t(i + r) * m + j), active, _mm256_loadu_si256(reinterpret_cast<const __m256i *>(result)));
}
}
}
if constexpr (rows == 4) {
if (i < n) product_packed_avx2<mod, 1>(a + std::size_t(i) * inner, b, c + std::size_t(i) * m, n - i, inner, m);
}
}
#endif
template <class T>
std::vector<T> multiply(const std::vector<T> &a, const std::vector<T> &b, int n, int inner, int m) {
std::vector<T> c(std::size_t(n) * m, T(0));
if (!n || !inner || !m) return c;
auto basic = [&] {
for (int i = 0; i < n; ++i) for (int k = 0; k < inner; ++k) {
const T &x = a[std::size_t(i) * inner + k];
for (int j = 0; j < m; ++j) c[std::size_t(i) * m + j] += x * b[std::size_t(k) * m + j];
}
};
if constexpr (small_modint<T>) {
if (std::uint64_t(n) * m <= 128u / unsigned(inner)) { basic(); return c; }
#if (defined(__x86_64__) || defined(__i386__)) && (defined(__GNUC__) || defined(__clang__))
if (inner >= 16 && m >= 8 && avx2()) {
const int block = avx512_product() ? 16 : 8;
std::vector<std::int32_t> x(a.size()), y(b.size());
std::vector<std::uint32_t> z(c.size());
auto center = [](std::uint32_t value) {
std::int32_t v = value;
return v <= std::int32_t((T::mod() - 1) / 2) ? v : v - std::int32_t(T::mod());
};
for (std::size_t i = 0; i < a.size(); ++i) x[i] = center(a[i].val());
for (int j = 0; j < m; j += block) {
int width = std::min(block, m - j);
for (int k = 0; k < inner; ++k) for (int s = 0; s < width; ++s)
y[std::size_t(j) * inner + std::size_t(k) * width + s] = center(b[std::size_t(k) * m + j + s].val());
}
if (block == 16) product_packed_avx512<T::mod()>(x.data(), y.data(), z.data(), n, inner, m);
else product_packed_avx2<T::mod()>(x.data(), y.data(), z.data(), n, inner, m);
for (std::size_t i = 0; i < c.size(); ++i) c[i] = T::raw(z[i]);
return c;
}
#endif
std::vector<std::uint32_t> x(a.size()), y(b.size()), z(c.size());
for (std::size_t i = 0; i < a.size(); ++i) x[i] = a[i].val();
for (std::size_t i = 0; i < b.size(); ++i) y[i] = b[i].val();
product_scalar<T::mod()>(x.data(), y.data(), z.data(), n, inner, m);
for (std::size_t i = 0; i < c.size(); ++i) c[i] = T::raw(z[i]);
} else basic();
return c;
}
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC pop_options
#endif
}
#line 8 "convolution/symmetric_group_convolution.hpp"
template <class Mint>
std::vector<Mint> symmetric_group_convolution(const SymmetricGroupFFT<Mint> &fft, const std::vector<Mint> &a, const std::vector<Mint> &b) {
auto x = fft.forward(a), y = fft.forward(b);
for (const auto &block : fft.blocks()) {
int d = block.dimension, offset = block.offset, length = d * d;
std::vector<Mint> left(x.begin() + offset, x.begin() + offset + length);
std::vector<Mint> right(y.begin() + offset, y.begin() + offset + length);
auto product = matrix_detail::multiply(left, right, d, d, d);
std::copy(product.begin(), product.end(), x.begin() + offset);
}
return fft.inverse(x);
}
template <class Mint>
std::vector<Mint> symmetric_group_convolution(const std::vector<Mint> &a, const std::vector<Mint> &b) {
int n = 0, size = 1;
while (std::size_t(size) < a.size() && n < 10) size *= ++n;
if (a.size() != std::size_t(size) || b.size() != a.size()) throw std::invalid_argument("symmetric_group_convolution size");
return symmetric_group_convolution(SymmetricGroupFFT<Mint>(n), a, b);
}