競技プログラミング用 C++ ライブラリ
components/fps_write.hpp · GitHub で見る
共通ページ
法 998244353 の共有 mint の係数列を Fast I/O でまとめて出力します。通常版・軽量版 FPS のどちらでも使え、FPS 本体を読み込まずにこの部品だけを include することもできます。
Linux x86_64 / GCC では AVX2 対応を自動で確認し、利用できる場合は SIMD で整形します。それ以外の CPU・コンパイラや Mac では、通常の FastOutput::write を使います。呼び出し側で CPU の判定をする必要はありません。
#include "lib/fps/fps_light.hpp"
#include "lib/components/fps_write.hpp"
int main() {
light_fps f{1, 2, 3};
const auto g = f.log(5).exp();
FastOutput out;
fps_detail::write_coefficients(out, g.data(), g.size());
return out.flush() ? 0 : 1;
}
出力を空白区切りで読むと、係数列は 1 2 3 0 0 です。整形のために入る空白の個数は実行環境で異なります。
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
fps_detail::write_coefficients(output,in,n) |
FastOutput& output へ、const mint* in の先頭 size_t n 係数を順に出力する。返り値は void
|
\(O(n)\) |
引数の順序は出力先・係数列の先頭・係数数です。既定引数はありません。n>0 なら、in から始まる添字 0 以上 n 未満の mint が有効であることが必要です。n=0 は in=nullptr でもよく、改行だけを出力します。
入力は変更しません。入力の寿命は呼び出しが終わるまで保ってください。入力のアラインメントや末尾の padding は要求せず、指定範囲外を読みません。同じ値や零を含む係数列も、その順序を保って出力します。係数型は共有する mint 専用です。mint::word で内部語を直接用意するときは、\(p=998244353\) として \([0,2p)\) に収めてください。
各係数を val() の十進整数値として空白で区切り、末尾に改行を付けます。SIMD 経路では、非零係数を含む 16 係数のまとまりを 9 桁分の領域に右詰めし、余白を空白にします。零だけのまとまりは 0 を並べます。通常の FastOutput::write の書式は変えません。
通常の output.write・output.println と混在できます。出力はバッファに残る場合があるため、最後に output.flush() を呼ぶか、output を破棄します。バッファを空ける処理や直接書き込みで失敗すると std::runtime_error を送出します。最後の flush() は成功時に true を返します。作業領域は呼び出しごとに所有し、共有する可変バッファはありません。同じ FastOutput を複数スレッドから同時に使わないでください。
係数数を \(n\) として時間は \(O(n)\)、追加の作業空間は \(O(1)\) です。空入力にも定数時間で対応します。
#ifndef CP_COMPONENTS_FPS_WRITE_HPP
#define CP_COMPONENTS_FPS_WRITE_HPP
#include "fast_io_output.hpp"
#include "../math/mint.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#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_io_fields {
namespace detail {
struct alignas(32) Lanes {
std::uint32_t lane[8];
};
constexpr Lanes splat(std::uint32_t x) { return {{x, x, x, x, x, x, x, x}}; }
constexpr Lanes all16(std::uint32_t x) { return splat(x * 0x10001); }
constexpr int Z = 0x80;
constexpr Lanes shuffle(const int (&b)[16]) {
Lanes t{};
for (int i = 0; i < 32; ++i) t.lane[i / 4] |= std::uint32_t(b[i % 16] & 0xFF) << 8 * (i % 4);
return t;
}
struct Constants {
Lanes div10 = splat(429496730);
Lanes div10e5 = splat(1407374884);
Lanes pack = splat(1 - 10000 * 65536u);
Lanes ten = splat(10);
Lanes tail = splat(' ' << 8 | '0');
Lanes by100 = all16(5243), hundred = all16(100);
Lanes by10 = all16(6554);
Lanes tens_out = all16(2559);
Lanes ones = splat(~0u), zero = splat(0x30303030), space = splat(0x20202020);
Lanes chunk0[2] = {shuffle({0, 1, 2, 3, 4, 5, 6, 7, Z, Z, 8, 9, 10, 11, 12, 13}),
shuffle({Z, Z, Z, Z, Z, Z, Z, Z, 0, 1, Z, Z, Z, Z, Z, Z})};
Lanes chunk1[3] = {shuffle({14, 15, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z}),
shuffle({Z, Z, 4, 5, Z, Z, Z, Z, Z, Z, Z, Z, 8, 9, Z, Z}),
shuffle({Z, Z, Z, Z, 0, 1, 2, 3, 4, 5, 6, 7, Z, Z, 8, 9})};
Lanes chunk2[3] = {shuffle({10, 11, 12, 13, 14, 15, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z}),
shuffle({Z, Z, Z, Z, Z, Z, 12, 13, Z, Z, Z, Z, Z, Z, Z, Z}),
shuffle({Z, Z, Z, Z, Z, Z, Z, Z, 0, 1, 2, 3, 4, 5, 6, 7})};
Lanes chunk3[3] = {shuffle({0, 1, Z, Z, Z, Z, Z, Z, Z, Z, 4, 5, Z, Z, Z, Z}),
shuffle({Z, Z, 8, 9, 10, 11, 12, 13, 14, 15, Z, Z, Z, Z, Z, Z}),
shuffle({Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, 0, 1, 2, 3})};
Lanes chunk4[2] = {shuffle({4, 5, 6, 7, Z, Z, 8, 9, 10, 11, 12, 13, 14, 15, Z, Z}),
shuffle({Z, Z, Z, Z, 8, 9, Z, Z, Z, Z, Z, Z, Z, Z, 12, 13})};
};
inline constexpr Constants kConstants{};
[[gnu::always_inline]] inline __m256i load(const Lanes& l) {
return _mm256_load_si256(reinterpret_cast<const __m256i*>(&l));
}
[[gnu::always_inline]] inline __m256i shuffle(__m256i x, const Lanes& control) {
return _mm256_shuffle_epi8(x, load(control));
}
struct Divided {
__m256i halves, tail;
};
[[gnu::always_inline]] inline Divided divide(__m256i v, __m256i odd, const Constants& k) {
const __m256i w = _mm256_blend_epi32(_mm256_srli_epi64(_mm256_mul_epu32(v, load(k.div10)), 32),
_mm256_mul_epu32(odd, load(k.div10)), 0xAA);
const __m256i h = _mm256_blend_epi32(_mm256_srli_epi64(_mm256_mul_epu32(v, load(k.div10e5)), 47),
_mm256_srli_epi64(_mm256_mul_epu32(odd, load(k.div10e5)), 15), 0xAA);
const __m256i halves = _mm256_add_epi32(_mm256_slli_epi32(w, 16), _mm256_mullo_epi32(h, load(k.pack)));
const __m256i tail = _mm256_sub_epi32(_mm256_add_epi32(v, load(k.tail)), _mm256_mullo_epi32(w, load(k.ten)));
return {halves, tail};
}
[[gnu::always_inline]] inline __m256i two_digits(__m256i z, const Constants& k) {
const __m256i tens = _mm256_mulhi_epu16(z, load(k.by10));
return _mm256_sub_epi16(_mm256_slli_epi16(z, 8), _mm256_mullo_epi16(tens, load(k.tens_out)));
}
[[gnu::always_inline]] inline __m256i text(__m256i digits, const Constants& k) {
const __m256i upto = _mm256_xor_si256(digits, _mm256_add_epi64(digits, load(k.ones)));
return _mm256_blendv_epi8(_mm256_or_si256(digits, load(k.zero)), load(k.space), upto);
}
[[gnu::always_inline]] inline void digits(const Divided& d, __m256i& lo, __m256i& hi, const Constants& k) {
const __m256i hundreds = _mm256_srli_epi16(_mm256_mulhi_epu16(d.halves, load(k.by100)), 3);
const __m256i rest = _mm256_sub_epi16(d.halves, _mm256_mullo_epi16(hundreds, load(k.hundred)));
lo = text(two_digits(_mm256_unpacklo_epi16(hundreds, rest), k), k);
hi = text(two_digits(_mm256_unpackhi_epi16(hundreds, rest), k), k);
}
[[gnu::always_inline]] inline void store(char* p, const Divided& lo, const Divided& hi, const Constants& k) {
__m256i d01, d23, d45, d67;
const auto either = [&](__m256i a, __m256i b) __attribute__((target("avx2"))) { return _mm256_or_si256(a, b); };
digits(lo, d01, d23, k);
digits(hi, d45, d67, k);
const __m256i c0 = either(shuffle(d01, k.chunk0[0]), shuffle(lo.tail, k.chunk0[1]));
const __m256i c1 = either(either(shuffle(d01, k.chunk1[0]), shuffle(lo.tail, k.chunk1[1])), shuffle(d23, k.chunk1[2]));
const __m256i c2 = either(either(shuffle(d23, k.chunk2[0]), shuffle(lo.tail, k.chunk2[1])), shuffle(d45, k.chunk2[2]));
const __m256i c3 = either(either(shuffle(hi.tail, k.chunk3[0]), shuffle(d45, k.chunk3[1])), shuffle(d67, k.chunk3[2]));
const __m256i c4 = either(shuffle(d67, k.chunk4[0]), shuffle(hi.tail, k.chunk4[1]));
const auto whole = [p](int c, __m256i chunk) __attribute__((target("avx2"))) { _mm256_storeu_si256(reinterpret_cast<__m256i*>(p + 64 + 16 * c), chunk); };
const auto low = [p](int c, __m256i chunk) __attribute__((target("avx2"))) { _mm_store_si128(reinterpret_cast<__m128i*>(p + 16 * c), _mm256_castsi256_si128(chunk)); };
whole(4, c4), whole(3, c3), whole(2, c2), whole(1, c1), whole(0, c0);
low(0, c0), low(1, c1), low(2, c2), low(3, c3), low(4, c4);
}
[[gnu::always_inline]] inline void divide16(const std::uint32_t* x, Divided& lo, Divided& hi, const Constants& k) {
const auto at = [x](int i) __attribute__((target("avx2"))) { return _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + i)); };
const auto both = [x](int i) __attribute__((target("avx2"))) { return _mm256_broadcastsi128_si256(_mm_loadu_si128(reinterpret_cast<const __m128i*>(x + i))); };
const __m256i l = _mm256_blend_epi32(at(0), both(8), 0xF0), h = _mm256_blend_epi32(at(8), both(4), 0x0F);
lo = divide(l, _mm256_srli_epi64(l, 32), k);
hi = divide(h, _mm256_srli_epi64(h, 32), k);
}
inline void format(char* p, const std::uint32_t* x, std::size_t count, const Constants& k) {
Divided dl, dh;
divide16(x, dl, dh, k);
for (std::size_t i = 16; i < count; i += 16, p += 160) {
Divided nl, nh;
divide16(x + i, nl, nh, k);
store(p, dl, dh, k);
dl = nl, dh = nh;
}
store(p, dl, dh, k);
}
}
inline constexpr std::size_t kBlock = 25600;
inline constexpr std::size_t kTextBytes = 10 * kBlock;
}
namespace fps_detail {
[[gnu::always_inline]] inline __m256i io_values(__m256i x) {
const auto p = _mm256_set1_epi32(mint::mod()), ni = _mm256_set1_epi32(998244351);
auto even = _mm256_and_si256(x, _mm256_set1_epi64x(0xffffffffULL));
auto odd = _mm256_srli_epi64(x, 32);
even = _mm256_add_epi64(even, _mm256_mul_epu32(_mm256_mul_epu32(even, ni), p));
odd = _mm256_add_epi64(odd, _mm256_mul_epu32(_mm256_mul_epu32(odd, ni), p));
const auto value = _mm256_blend_epi32(_mm256_srli_epi64(even, 32), odd, 0xaa);
return _mm256_min_epu32(value, _mm256_sub_epi32(value, p));
}
inline void write_coefficients_native(FastOutput &output, const mint *in, std::size_t n) {
alignas(32) unsigned values[25600];
alignas(32) char text[256000];
const auto &k = fps_io_fields::detail::kConstants;
for (std::size_t i = 0; i < n; i += 25600) {
const auto count = std::min<std::size_t>(25600, n - i), full = count / 16 * 16;
char *end = text;
static constexpr char zeros[] = "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ";
const auto empty = [&]() __attribute__((target("avx2"))) {
std::size_t at = 0;
const auto p = _mm256_set1_epi32(mint::mod());
for (; at + 8 <= count; at += 8) {
__m256i x;
std::memcpy(&x, in + i + at, sizeof(x));
x = _mm256_min_epu32(x, _mm256_sub_epi32(x, p));
if (!_mm256_testz_si256(x, x)) return false;
}
for (; at < count; ++at) if (in[i + at] != mint(0)) return false;
return true;
};
if (empty()) {
for (std::size_t at = 0; at < count; at += 16) std::memcpy(text + 2 * at, zeros, 32);
end += 2 * count;
} else {
std::size_t j = 0;
for (; j + 8 <= count; j += 8) {
alignas(32) unsigned words[8];
std::memcpy(words, in + i + j, sizeof(words));
_mm256_storeu_si256(reinterpret_cast<__m256i *>(values + j), io_values(_mm256_load_si256(reinterpret_cast<const __m256i *>(words))));
}
for (; j < count; ++j) values[j] = in[i + j].val();
auto zero = [&](std::size_t at) __attribute__((target("avx2"))) {
auto x = _mm256_or_si256(_mm256_loadu_si256(reinterpret_cast<const __m256i *>(values + at)), _mm256_loadu_si256(reinterpret_cast<const __m256i *>(values + at + 8)));
return _mm256_testz_si256(x, x);
};
std::size_t at = 0;
while (at < full) {
if (zero(at)) { std::memcpy(end, zeros, 32); end += 32; at += 16; }
else {
const auto first = at;
do at += 16; while (at < full && !zero(at));
fps_io_fields::detail::format(end, values + first, at - first, k);
end += 10 * (at - first);
}
}
if (full < count) {
alignas(32) unsigned rest[16] = {};
alignas(16) char last[160];
std::copy_n(values + full, count - full, rest);
bool empty = true;
for (auto x : rest) empty &= x == 0;
if (empty) { std::memcpy(end, zeros, 2 * (count - full)); end += 2 * (count - full); }
else {
fps_io_fields::detail::format(last, rest, 16, k);
std::memcpy(end, last, 10 * (count - full));
end += 10 * (count - full);
}
}
}
if (i + count == n) end[-1] = '\n';
fast_io_detail::BulkOutput::write(output, std::string_view(text, end - text));
}
if (!n) output.write('\n');
}
}
#pragma GCC pop_options
#endif
namespace fps_detail {
inline void write_coefficients(FastOutput &output, const mint *in, std::size_t n) {
#if defined(__linux__) && defined(__x86_64__) && defined(__GNUC__) && !defined(__clang__)
if (__builtin_cpu_supports("avx2")) return write_coefficients_native(output, in, n);
#endif
for (std::size_t i = 0; i < n; ++i) { output.write(in[i].val()); output.write(' '); }
output.write('\n');
}
}
#endif
#line 1 "components/fps_write.hpp"
#line 1 "components/fast_io_output.hpp"
#line 1 "components/fast_io.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <cassert>
#include <charconv>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#if defined(__unix__) || defined(__APPLE__)
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
namespace fast_io_detail { struct BulkInput; struct BulkOutput; }
class FastInput {
friend struct fast_io_detail::BulkInput;
static constexpr std::size_t capacity = 1 << 16;
FILE *file = nullptr;
std::unique_ptr<char[]> buffer;
const char *current = "", *end = current;
std::string scratch;
void *mapping = nullptr;
std::size_t mapped_size = 0;
bool refill() {
if (!file) return false;
std::size_t n = std::fread(buffer.get(), 1, capacity, file);
current = buffer.get();
end = current + n;
return n;
}
static const char *scan(const char *p, const char *end) {
while (end - p >= 8) {
std::uint64_t x;
std::memcpy(&x, p, 8);
if ((x - 0x2121212121212121ULL) & ~x & 0x8080808080808080ULL) break;
p += 8;
}
while (p != end && static_cast<unsigned char>(*p) > ' ') ++p;
return p;
}
public:
explicit FastInput(FILE *input = stdin) {
#if defined(__unix__) || defined(__APPLE__)
struct stat status;
int fd = fileno(input);
auto offset = ftello(input);
if (fd >= 0 && offset >= 0 && !fstat(fd, &status) && S_ISREG(status.st_mode) && status.st_size > offset) {
mapped_size = status.st_size;
mapping = mmap(nullptr, mapped_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapping != MAP_FAILED) {
current = static_cast<const char *>(mapping) + offset;
end = static_cast<const char *>(mapping) + mapped_size;
return;
}
mapping = nullptr;
}
#endif
file = input;
buffer = std::make_unique_for_overwrite<char[]>(capacity);
}
explicit FastInput(std::string_view input) : current(input.empty() ? "" : input.data()), end(current + input.size()) {}
FastInput(const FastInput &) = delete;
FastInput &operator=(const FastInput &) = delete;
~FastInput() {
#if defined(__unix__) || defined(__APPLE__)
if (mapping) munmap(mapping, mapped_size);
#endif
}
std::string_view token() {
while (true) {
while (current != end && static_cast<unsigned char>(*current) <= ' ') ++current;
if (current != end) break;
if (!refill()) return {};
}
const char *begin = current;
current = scan(current, end);
if (current != end || !file) return {begin, std::size_t(current - begin)};
scratch.assign(begin, end);
while (refill()) {
current = scan(current, end);
scratch.append(buffer.get(), std::size_t(current - buffer.get()));
if (current != end) break;
}
return scratch;
}
template <class T>
bool read(T &value) {
if constexpr (std::is_integral_v<T> && !std::is_same_v<T, bool>) {
while (true) {
while (current != end && static_cast<unsigned char>(*current) <= ' ') ++current;
if (current != end) break;
if (!refill()) return false;
}
if constexpr (std::is_unsigned_v<T> && sizeof(T) == 4 && std::endian::native == std::endian::little) {
if (end - current >= 11) {
std::uint64_t x;
std::memcpy(&x, current, 8);
if (!(((x + 0x4646464646464646ULL) | (x - 0x3030303030303030ULL)) & 0x8080808080808080ULL)) {
x &= 0x0f0f0f0f0f0f0f0fULL;
x = (x * 10 + (x >> 8)) & 0x00ff00ff00ff00ffULL;
x = (x * 100 + (x >> 16)) & 0x0000ffff0000ffffULL;
std::uint64_t v = std::uint32_t(x * 10000 + (x >> 32));
int n = 8;
for (; n < 10 && static_cast<unsigned char>(current[n]) - unsigned('0') < 10; ++n)
v = v * 10 + (current[n] - '0');
if (static_cast<unsigned char>(current[n]) <= ' ' && v <= 0xffffffffULL) {
current += n;
value = T(v);
return true;
}
}
}
}
T parsed{};
auto result = std::from_chars(current, end, parsed);
if (result.ec == std::errc{} && (result.ptr != end ? static_cast<unsigned char>(*result.ptr) <= ' ' : !file)) {
current = result.ptr;
value = parsed;
return true;
}
}
auto s = token();
if (s.empty()) return false;
if constexpr (std::is_same_v<T, bool>) {
assert(s == "0" || s == "1");
value = s[0] == '1';
} else if constexpr (std::is_integral_v<T>) {
auto result = std::from_chars(s.data(), s.data() + s.size(), value);
assert(result.ec == std::errc{} && result.ptr == s.data() + s.size());
(void)result;
} else value = T(s);
return true;
}
};
class FastOutput {
friend struct fast_io_detail::BulkOutput;
inline static constexpr auto quads = [] {
std::array<std::array<char, 4>, 10000> table{};
for (int i = 0; i < 10000; ++i)
table[i] = {char('0' + i / 1000), char('0' + i / 100 % 10), char('0' + i / 10 % 10), char('0' + i % 10)};
return table;
}();
FILE *file;
std::unique_ptr<char[]> buffer = std::make_unique_for_overwrite<char[]>(1 << 16);
std::size_t capacity = 1 << 16, length = 0;
char *reserve(std::size_t n) {
if (n > capacity - length) {
if (!flush()) throw std::runtime_error("output failed");
if (n > capacity) {
std::size_t next = std::max(n, 2 * capacity);
buffer = std::make_unique_for_overwrite<char[]>(next);
capacity = next;
}
}
return buffer.get() + length;
}
public:
explicit FastOutput(FILE *output = stdout) : file(output) {}
FastOutput(const FastOutput &) = delete;
FastOutput &operator=(const FastOutput &) = delete;
~FastOutput() { flush(); }
bool flush() {
std::size_t offset = 0;
while (offset < length) {
auto n = std::fwrite(buffer.get() + offset, 1, length - offset, file);
if (!n) {
std::memmove(buffer.get(), buffer.get() + offset, length - offset);
length -= offset;
return false;
}
offset += n;
}
length = 0;
return std::fflush(file) == 0;
}
void write(char c) { *reserve(1) = c; ++length; }
void write(__uint128_t value) {
char digits[40];
char *end = digits + sizeof(digits), *begin = end;
do { *--begin = char('0' + value % 10); value /= 10; } while (value);
write(std::string_view(begin, end - begin));
}
void write(__int128_t value) {
__uint128_t magnitude = __uint128_t(value);
if (value < 0) write('-'), magnitude = 0 - magnitude;
write(magnitude);
}
void write(std::string_view s) {
char *out = reserve(s.size());
if (!s.empty()) std::memcpy(out, s.data(), s.size());
length += s.size();
}
template <class T> requires std::is_integral_v<T>
void write(T value) {
char *out = reserve(32);
if constexpr (std::is_same_v<T, bool>) *out = '0' + value, ++length;
else if constexpr (std::is_unsigned_v<T> && sizeof(T) == 4) {
char *p = out;
if (value >= 100000000) {
auto high = value / 100000000, low = value % 100000000;
p = std::to_chars(p, p + 2, high).ptr;
std::memcpy(p, quads[low / 10000].data(), 4);
std::memcpy(p + 4, quads[low % 10000].data(), 4);
p += 8;
} else if (value >= 10000) {
p = std::to_chars(p, p + 4, value / 10000).ptr;
std::memcpy(p, quads[value % 10000].data(), 4);
p += 4;
} else p = std::to_chars(p, p + 4, value).ptr;
length += p - out;
} else length += std::to_chars(out, out + 32, value).ptr - out;
}
template <class T> requires requires(const T &value, char *out) { value.decimal_capacity(); value.to_chars(out); }
void write(const T &value) {
char *out = reserve(value.decimal_capacity());
length += value.to_chars(out) - out;
}
template <class T>
void println(const T &value) { write(value); write('\n'); }
};
#line 5 "components/fast_io_output.hpp"
#include <limits>
#include <utility>
namespace fast_io_detail {
struct BulkOutput {
static char *small(char *out, std::uint32_t value) {
unsigned n = 1 + (value >= 10) + (value >= 100) + (value >= 1000);
if constexpr (std::endian::native == std::endian::little) {
std::uint32_t word;
std::memcpy(&word, FastOutput::quads[value].data(), 4);
word >>= (4 - n) * 8;
std::memcpy(out, &word, 4);
} else std::memcpy(out, FastOutput::quads[value].data() + 4 - n, n);
return out + n;
}
static char *number(char *out, std::uint32_t value) {
if (value >= 100000000) {
out = small(out, value / 100000000);
value %= 100000000;
std::memcpy(out, FastOutput::quads[value / 10000].data(), 4);
std::memcpy(out + 4, FastOutput::quads[value % 10000].data(), 4);
return out + 8;
}
if (value >= 10000) {
out = small(out, value / 10000);
std::memcpy(out, FastOutput::quads[value % 10000].data(), 4);
return out + 4;
}
return small(out, value);
}
template<std::size_t Fields, class Read>
#if defined(__GNUC__) || defined(__clang__)
[[gnu::noinline]]
#endif
static void records(FastOutput &out, std::size_t count, Read read) {
constexpr auto bytes = 11 * Fields;
constexpr auto block = std::max<std::size_t>(1, (1 << 16) / bytes);
for (std::size_t base = 0; base < count;) {
const auto n = std::min(count - base, block);
char *begin = out.reserve(n * bytes), *p = begin;
for (std::size_t i = 0; i < n; ++i) {
auto values = read(base + i);
for (std::size_t j = 0; j < Fields; ++j) {
p = number(p, std::uint32_t(values[j]));
*p++ = j + 1 == Fields ? '\n' : ' ';
}
}
out.length += p - begin;
base += n;
}
}
static void write(FastOutput &out, std::string_view text) {
if (text.size() <= (1 << 16)) return out.write(text);
if (out.length && !out.flush()) throw std::runtime_error("output failed");
std::size_t done = 0;
while (done < text.size()) {
const auto written = std::fwrite(text.data() + done, 1, text.size() - done, out.file);
if (!written) throw std::runtime_error("output failed");
done += written;
}
}
};
}
template<std::size_t Fields, class Read>
void write_unsigned_records(FastOutput &output, std::size_t count, Read read) {
static_assert(Fields > 0 && Fields <= std::numeric_limits<std::size_t>::max() / 11);
fast_io_detail::BulkOutput::records<Fields>(output, count, std::move(read));
}
#line 1 "math/mint.hpp"
#line 4 "math/mint.hpp"
#include <istream>
#include <ostream>
#line 7 "math/mint.hpp"
#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 10 "components/fps_write.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_io_fields {
namespace detail {
struct alignas(32) Lanes {
std::uint32_t lane[8];
};
constexpr Lanes splat(std::uint32_t x) { return {{x, x, x, x, x, x, x, x}}; }
constexpr Lanes all16(std::uint32_t x) { return splat(x * 0x10001); }
constexpr int Z = 0x80;
constexpr Lanes shuffle(const int (&b)[16]) {
Lanes t{};
for (int i = 0; i < 32; ++i) t.lane[i / 4] |= std::uint32_t(b[i % 16] & 0xFF) << 8 * (i % 4);
return t;
}
struct Constants {
Lanes div10 = splat(429496730);
Lanes div10e5 = splat(1407374884);
Lanes pack = splat(1 - 10000 * 65536u);
Lanes ten = splat(10);
Lanes tail = splat(' ' << 8 | '0');
Lanes by100 = all16(5243), hundred = all16(100);
Lanes by10 = all16(6554);
Lanes tens_out = all16(2559);
Lanes ones = splat(~0u), zero = splat(0x30303030), space = splat(0x20202020);
Lanes chunk0[2] = {shuffle({0, 1, 2, 3, 4, 5, 6, 7, Z, Z, 8, 9, 10, 11, 12, 13}),
shuffle({Z, Z, Z, Z, Z, Z, Z, Z, 0, 1, Z, Z, Z, Z, Z, Z})};
Lanes chunk1[3] = {shuffle({14, 15, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z}),
shuffle({Z, Z, 4, 5, Z, Z, Z, Z, Z, Z, Z, Z, 8, 9, Z, Z}),
shuffle({Z, Z, Z, Z, 0, 1, 2, 3, 4, 5, 6, 7, Z, Z, 8, 9})};
Lanes chunk2[3] = {shuffle({10, 11, 12, 13, 14, 15, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z}),
shuffle({Z, Z, Z, Z, Z, Z, 12, 13, Z, Z, Z, Z, Z, Z, Z, Z}),
shuffle({Z, Z, Z, Z, Z, Z, Z, Z, 0, 1, 2, 3, 4, 5, 6, 7})};
Lanes chunk3[3] = {shuffle({0, 1, Z, Z, Z, Z, Z, Z, Z, Z, 4, 5, Z, Z, Z, Z}),
shuffle({Z, Z, 8, 9, 10, 11, 12, 13, 14, 15, Z, Z, Z, Z, Z, Z}),
shuffle({Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, 0, 1, 2, 3})};
Lanes chunk4[2] = {shuffle({4, 5, 6, 7, Z, Z, 8, 9, 10, 11, 12, 13, 14, 15, Z, Z}),
shuffle({Z, Z, Z, Z, 8, 9, Z, Z, Z, Z, Z, Z, Z, Z, 12, 13})};
};
inline constexpr Constants kConstants{};
[[gnu::always_inline]] inline __m256i load(const Lanes& l) {
return _mm256_load_si256(reinterpret_cast<const __m256i*>(&l));
}
[[gnu::always_inline]] inline __m256i shuffle(__m256i x, const Lanes& control) {
return _mm256_shuffle_epi8(x, load(control));
}
struct Divided {
__m256i halves, tail;
};
[[gnu::always_inline]] inline Divided divide(__m256i v, __m256i odd, const Constants& k) {
const __m256i w = _mm256_blend_epi32(_mm256_srli_epi64(_mm256_mul_epu32(v, load(k.div10)), 32),
_mm256_mul_epu32(odd, load(k.div10)), 0xAA);
const __m256i h = _mm256_blend_epi32(_mm256_srli_epi64(_mm256_mul_epu32(v, load(k.div10e5)), 47),
_mm256_srli_epi64(_mm256_mul_epu32(odd, load(k.div10e5)), 15), 0xAA);
const __m256i halves = _mm256_add_epi32(_mm256_slli_epi32(w, 16), _mm256_mullo_epi32(h, load(k.pack)));
const __m256i tail = _mm256_sub_epi32(_mm256_add_epi32(v, load(k.tail)), _mm256_mullo_epi32(w, load(k.ten)));
return {halves, tail};
}
[[gnu::always_inline]] inline __m256i two_digits(__m256i z, const Constants& k) {
const __m256i tens = _mm256_mulhi_epu16(z, load(k.by10));
return _mm256_sub_epi16(_mm256_slli_epi16(z, 8), _mm256_mullo_epi16(tens, load(k.tens_out)));
}
[[gnu::always_inline]] inline __m256i text(__m256i digits, const Constants& k) {
const __m256i upto = _mm256_xor_si256(digits, _mm256_add_epi64(digits, load(k.ones)));
return _mm256_blendv_epi8(_mm256_or_si256(digits, load(k.zero)), load(k.space), upto);
}
[[gnu::always_inline]] inline void digits(const Divided& d, __m256i& lo, __m256i& hi, const Constants& k) {
const __m256i hundreds = _mm256_srli_epi16(_mm256_mulhi_epu16(d.halves, load(k.by100)), 3);
const __m256i rest = _mm256_sub_epi16(d.halves, _mm256_mullo_epi16(hundreds, load(k.hundred)));
lo = text(two_digits(_mm256_unpacklo_epi16(hundreds, rest), k), k);
hi = text(two_digits(_mm256_unpackhi_epi16(hundreds, rest), k), k);
}
[[gnu::always_inline]] inline void store(char* p, const Divided& lo, const Divided& hi, const Constants& k) {
__m256i d01, d23, d45, d67;
const auto either = [&](__m256i a, __m256i b) __attribute__((target("avx2"))) { return _mm256_or_si256(a, b); };
digits(lo, d01, d23, k);
digits(hi, d45, d67, k);
const __m256i c0 = either(shuffle(d01, k.chunk0[0]), shuffle(lo.tail, k.chunk0[1]));
const __m256i c1 = either(either(shuffle(d01, k.chunk1[0]), shuffle(lo.tail, k.chunk1[1])), shuffle(d23, k.chunk1[2]));
const __m256i c2 = either(either(shuffle(d23, k.chunk2[0]), shuffle(lo.tail, k.chunk2[1])), shuffle(d45, k.chunk2[2]));
const __m256i c3 = either(either(shuffle(hi.tail, k.chunk3[0]), shuffle(d45, k.chunk3[1])), shuffle(d67, k.chunk3[2]));
const __m256i c4 = either(shuffle(d67, k.chunk4[0]), shuffle(hi.tail, k.chunk4[1]));
const auto whole = [p](int c, __m256i chunk) __attribute__((target("avx2"))) { _mm256_storeu_si256(reinterpret_cast<__m256i*>(p + 64 + 16 * c), chunk); };
const auto low = [p](int c, __m256i chunk) __attribute__((target("avx2"))) { _mm_store_si128(reinterpret_cast<__m128i*>(p + 16 * c), _mm256_castsi256_si128(chunk)); };
whole(4, c4), whole(3, c3), whole(2, c2), whole(1, c1), whole(0, c0);
low(0, c0), low(1, c1), low(2, c2), low(3, c3), low(4, c4);
}
[[gnu::always_inline]] inline void divide16(const std::uint32_t* x, Divided& lo, Divided& hi, const Constants& k) {
const auto at = [x](int i) __attribute__((target("avx2"))) { return _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + i)); };
const auto both = [x](int i) __attribute__((target("avx2"))) { return _mm256_broadcastsi128_si256(_mm_loadu_si128(reinterpret_cast<const __m128i*>(x + i))); };
const __m256i l = _mm256_blend_epi32(at(0), both(8), 0xF0), h = _mm256_blend_epi32(at(8), both(4), 0x0F);
lo = divide(l, _mm256_srli_epi64(l, 32), k);
hi = divide(h, _mm256_srli_epi64(h, 32), k);
}
inline void format(char* p, const std::uint32_t* x, std::size_t count, const Constants& k) {
Divided dl, dh;
divide16(x, dl, dh, k);
for (std::size_t i = 16; i < count; i += 16, p += 160) {
Divided nl, nh;
divide16(x + i, nl, nh, k);
store(p, dl, dh, k);
dl = nl, dh = nh;
}
store(p, dl, dh, k);
}
}
inline constexpr std::size_t kBlock = 25600;
inline constexpr std::size_t kTextBytes = 10 * kBlock;
}
namespace fps_detail {
[[gnu::always_inline]] inline __m256i io_values(__m256i x) {
const auto p = _mm256_set1_epi32(mint::mod()), ni = _mm256_set1_epi32(998244351);
auto even = _mm256_and_si256(x, _mm256_set1_epi64x(0xffffffffULL));
auto odd = _mm256_srli_epi64(x, 32);
even = _mm256_add_epi64(even, _mm256_mul_epu32(_mm256_mul_epu32(even, ni), p));
odd = _mm256_add_epi64(odd, _mm256_mul_epu32(_mm256_mul_epu32(odd, ni), p));
const auto value = _mm256_blend_epi32(_mm256_srli_epi64(even, 32), odd, 0xaa);
return _mm256_min_epu32(value, _mm256_sub_epi32(value, p));
}
inline void write_coefficients_native(FastOutput &output, const mint *in, std::size_t n) {
alignas(32) unsigned values[25600];
alignas(32) char text[256000];
const auto &k = fps_io_fields::detail::kConstants;
for (std::size_t i = 0; i < n; i += 25600) {
const auto count = std::min<std::size_t>(25600, n - i), full = count / 16 * 16;
char *end = text;
static constexpr char zeros[] = "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ";
const auto empty = [&]() __attribute__((target("avx2"))) {
std::size_t at = 0;
const auto p = _mm256_set1_epi32(mint::mod());
for (; at + 8 <= count; at += 8) {
__m256i x;
std::memcpy(&x, in + i + at, sizeof(x));
x = _mm256_min_epu32(x, _mm256_sub_epi32(x, p));
if (!_mm256_testz_si256(x, x)) return false;
}
for (; at < count; ++at) if (in[i + at] != mint(0)) return false;
return true;
};
if (empty()) {
for (std::size_t at = 0; at < count; at += 16) std::memcpy(text + 2 * at, zeros, 32);
end += 2 * count;
} else {
std::size_t j = 0;
for (; j + 8 <= count; j += 8) {
alignas(32) unsigned words[8];
std::memcpy(words, in + i + j, sizeof(words));
_mm256_storeu_si256(reinterpret_cast<__m256i *>(values + j), io_values(_mm256_load_si256(reinterpret_cast<const __m256i *>(words))));
}
for (; j < count; ++j) values[j] = in[i + j].val();
auto zero = [&](std::size_t at) __attribute__((target("avx2"))) {
auto x = _mm256_or_si256(_mm256_loadu_si256(reinterpret_cast<const __m256i *>(values + at)), _mm256_loadu_si256(reinterpret_cast<const __m256i *>(values + at + 8)));
return _mm256_testz_si256(x, x);
};
std::size_t at = 0;
while (at < full) {
if (zero(at)) { std::memcpy(end, zeros, 32); end += 32; at += 16; }
else {
const auto first = at;
do at += 16; while (at < full && !zero(at));
fps_io_fields::detail::format(end, values + first, at - first, k);
end += 10 * (at - first);
}
}
if (full < count) {
alignas(32) unsigned rest[16] = {};
alignas(16) char last[160];
std::copy_n(values + full, count - full, rest);
bool empty = true;
for (auto x : rest) empty &= x == 0;
if (empty) { std::memcpy(end, zeros, 2 * (count - full)); end += 2 * (count - full); }
else {
fps_io_fields::detail::format(last, rest, 16, k);
std::memcpy(end, last, 10 * (count - full));
end += 10 * (count - full);
}
}
}
if (i + count == n) end[-1] = '\n';
fast_io_detail::BulkOutput::write(output, std::string_view(text, end - text));
}
if (!n) output.write('\n');
}
}
#pragma GCC pop_options
#endif
namespace fps_detail {
inline void write_coefficients(FastOutput &output, const mint *in, std::size_t n) {
#if defined(__linux__) && defined(__x86_64__) && defined(__GNUC__) && !defined(__clang__)
if (__builtin_cpu_supports("avx2")) return write_coefficients_native(output, in, n);
#endif
for (std::size_t i = 0; i < n; ++i) { output.write(in[i].val()); output.write(' '); }
output.write('\n');
}
}