Felix's Library

This documentation is automatically generated by online-judge-tools/verification-helper

View the Project on GitHub fffelix-huang/CP-stuff

:heavy_check_mark: test/formal-power-series/poly/yosupo-Product-of-Polynomial-Sequence.test.cpp

Depends on

Code

#define PROBLEM "https://judge.yosupo.jp/problem/product_of_polynomial_sequence"

#include <iostream>

#include <queue>

#include "../../../library/formal-power-series/poly.hpp"

using namespace std;
using namespace felix;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin >> n;
	if(n == 0) {
		cout << "1\n";
		return 0;
	}
	vector<Poly<998244353>> a(n);
	for(int i = 0; i < n; i++) {
		int m;
		cin >> m;
		a[i].resize(m + 1);
		for(int j = 0; j <= m; j++) {
			cin >> a[i][j];
		}
	}
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
	for(int i = 0; i < n; i++) {
		pq.emplace(a[i].size(), i);
	}
	while(pq.size() > 1) {
		auto [x, i] = pq.top();
		pq.pop();
		auto [y, j] = pq.top();
		pq.pop();
		a[i] *= a[j];
		pq.emplace(a[i].size(), i);
	}
	auto id = pq.top().second;
	for(int i = 0; i < a[id].size(); i++) {
		cout << a[id][i] << " \n"[i == a[id].size() - 1];
	}
	return 0;
}
#line 1 "test/formal-power-series/poly/yosupo-Product-of-Polynomial-Sequence.test.cpp"
#define PROBLEM "https://judge.yosupo.jp/problem/product_of_polynomial_sequence"

#include <iostream>

#include <queue>

#line 2 "library/formal-power-series/poly.hpp"
#include <vector>

#include <initializer_list>

#include <algorithm>

#include <functional>

#include <cassert>

#line 6 "library/modint/modint.hpp"
#include <type_traits>

#line 3 "library/misc/type-traits.hpp"
#include <numeric>

#line 5 "library/misc/type-traits.hpp"

namespace felix {

namespace internal {

#ifndef _MSC_VER
template<class T> using is_signed_int128 = typename std::conditional<std::is_same<T, __int128_t>::value || std::is_same<T, __int128>::value, std::true_type, std::false_type>::type;
template<class T> using is_unsigned_int128 = typename std::conditional<std::is_same<T, __uint128_t>::value || std::is_same<T, unsigned __int128>::value, std::true_type, std::false_type>::type;
template<class T> using make_unsigned_int128 = typename std::conditional<std::is_same<T, __int128_t>::value, __uint128_t, unsigned __int128>;
template<class T> using is_integral = typename std::conditional<std::is_integral<T>::value || is_signed_int128<T>::value || is_unsigned_int128<T>::value, std::true_type, std::false_type>::type;
template<class T> using is_signed_int = typename std::conditional<(is_integral<T>::value && std::is_signed<T>::value) || is_signed_int128<T>::value, std::true_type, std::false_type>::type;
template<class T> using is_unsigned_int = typename std::conditional<(is_integral<T>::value && std::is_unsigned<T>::value) || is_unsigned_int128<T>::value, std::true_type, std::false_type>::type;
template<class T> using to_unsigned = typename std::conditional<is_signed_int128<T>::value, make_unsigned_int128<T>, typename std::conditional<std::is_signed<T>::value, std::make_unsigned<T>, std::common_type<T>>::type>::type;
#else
template<class T> using is_integral = typename std::is_integral<T>;
template<class T> using is_signed_int = typename std::conditional<is_integral<T>::value && std::is_signed<T>::value, std::true_type, std::false_type>::type;
template<class T> using is_unsigned_int = typename std::conditional<is_integral<T>::value && std::is_unsigned<T>::value, std::true_type, std::false_type>::type;
template<class T> using to_unsigned = typename std::conditional<is_signed_int<T>::value, std::make_unsigned<T>, std::common_type<T>>::type;
#endif

template<class T> using is_signed_int_t = std::enable_if_t<is_signed_int<T>::value>;
template<class T> using is_unsigned_int_t = std::enable_if_t<is_unsigned_int<T>::value>;
template<class T> using to_unsigned_t = typename to_unsigned<T>::type;

template<class T> struct safely_multipliable {};
template<> struct safely_multipliable<short> { using type = int; };
template<> struct safely_multipliable<unsigned short> { using type = unsigned int; };
template<> struct safely_multipliable<int> { using type = long long; };
template<> struct safely_multipliable<unsigned int> { using type = unsigned long long; };
template<> struct safely_multipliable<long long> { using type = __int128; };
template<> struct safely_multipliable<unsigned long long> { using type = __uint128_t; };

template<class T> using safely_multipliable_t = typename safely_multipliable<T>::type;

}  // namespace internal


}  // namespace felix

#line 2 "library/math/safe-mod.hpp"

namespace felix {

namespace internal {

template<class T>
constexpr T safe_mod(T x, T m) {
	x %= m;
	if(x < 0) {
		x += m;
	}
	return x;
}

} // namespace internal


} // namespace felix
#line 3 "library/math/inv-gcd.hpp"

namespace felix {

namespace internal {

template<class T>
constexpr std::pair<T, T> inv_gcd(T a, T b) {
	a = safe_mod(a, b);
	if(a == 0) {
		return {b, 0};
	}
	T s = b, t = a;
	T m0 = 0, m1 = 1;
	while(t) {
		T u = s / t;
		s -= t * u;
		m0 -= m1 * u;
		auto tmp = s;
		s = t;
		t = tmp;
		tmp = m0;
		m0 = m1;
		m1 = tmp;
	}
	if(m0 < 0) {
		m0 += b / s;
	}
	return {s, m0};
}

} // namespace internal


} // namespace felix

#line 9 "library/modint/modint.hpp"

namespace felix {

template<int id>
struct modint {
public:
	static constexpr int mod() { return (id > 0 ? id : md); }
 	
	static constexpr void set_mod(int m) {
		if(id > 0 || md == m) {
			return;
		}
		md = m;
		fact.resize(1);
		inv_fact.resize(1);
		invs.resize(1);
	}

	static constexpr void prepare(int n) {
		int sz = (int) fact.size();
		if(sz == mod()) {
			return;
		}
		n = 1 << std::__lg(2 * n - 1);
		if(n < sz) {
			return;
		}
		if(n < (sz - 1) * 2) {
			n = std::min((sz - 1) * 2, mod() - 1);
		}
		fact.resize(n + 1);
		inv_fact.resize(n + 1);
		invs.resize(n + 1);
		for(int i = sz; i <= n; i++) {
			fact[i] = fact[i - 1] * i;
		}
		auto eg = internal::inv_gcd(fact.back().val(), mod());
		assert(eg.first == 1);
		inv_fact[n] = eg.second;
		for(int i = n - 1; i >= sz; i--) {
			inv_fact[i] = inv_fact[i + 1] * (i + 1);
		}
		for(int i = n; i >= sz; i--) {
			invs[i] = inv_fact[i] * fact[i - 1];
		}
	}
 
	constexpr modint() : v(0) {} 
	template<class T, internal::is_signed_int_t<T>* = nullptr> constexpr modint(T x) : v(x >= 0 ? x % mod() : x % mod() + mod()) {}
	template<class T, internal::is_unsigned_int_t<T>* = nullptr> constexpr modint(T x) : v(x % mod()) {}
 
	constexpr int val() const { return v; }

	constexpr modint inv() const {
		if(id > 0 && v < std::min(mod() >> 1, 1 << 18)) {
			prepare(v);
			return invs[v];
		} else {
			auto eg = internal::inv_gcd(v, mod());
			assert(eg.first == 1);
			return eg.second;
		}
	}
 
	constexpr modint& operator+=(const modint& rhs) & {
		v += rhs.v;
		if(v >= mod()) {
			v -= mod();
		}
		return *this;
	}
 
	constexpr modint& operator-=(const modint& rhs) & {
		v -= rhs.v;
		if(v < 0) {
			v += mod();
		}
		return *this;
	}

	constexpr modint& operator*=(const modint& rhs) & {
		v = 1LL * v * rhs.v % mod();
		return *this;
	}

	constexpr modint& operator/=(const modint& rhs) & {
		return *this *= rhs.inv();
	}

	friend constexpr modint operator+(modint lhs, modint rhs) { return lhs += rhs; }
	friend constexpr modint operator-(modint lhs, modint rhs) { return lhs -= rhs; }
	friend constexpr modint operator*(modint lhs, modint rhs) { return lhs *= rhs; }
	friend constexpr modint operator/(modint lhs, modint rhs) { return lhs /= rhs; }

	constexpr modint operator+() const { return *this; }
	constexpr modint operator-() const { return modint() - *this; } 
	constexpr bool operator==(const modint& rhs) const { return v == rhs.v; } 
	constexpr bool operator!=(const modint& rhs) const { return v != rhs.v; }

	constexpr modint pow(long long p) const {
		modint a(*this), res(1);
		if(p < 0) {
			a = a.inv();
			p = -p;
		}
		while(p) {
			if(p & 1) {
				res *= a;
			}
			a *= a;
			p >>= 1;
		}
		return res;
	}

	constexpr bool has_sqrt() const {
		if(mod() == 2 || v == 0) {
			return true;
		}
		if(pow((mod() - 1) / 2).val() != 1) {
			return false;
		}
		return true;
	}

	constexpr modint sqrt() const {
		if(mod() == 2 || v < 2) {
			return *this;
		}
		assert(pow((mod() - 1) / 2).val() == 1);
		modint b = 1;
		while(b.pow((mod() - 1) >> 1).val() == 1) {
			b += 1;
		}
		int m = mod() - 1, e = __builtin_ctz(m);
		m >>= e;
		modint x = modint(*this).pow((m - 1) >> 1);
		modint y = modint(*this) * x * x;
		x *= v;
		modint z = b.pow(m);
		while(y.val() != 1) {
			int j = 0;
			modint t = y;
			while(t.val() != 1) {
				t *= t;
				j++;
			}
			z = z.pow(1LL << (e - j - 1));
			x *= z, z *= z, y *= z;
			e = j;
		}
		return x;
	}

	friend std::istream& operator>>(std::istream& in, modint& num) {
		long long x;
		in >> x;
		num = modint<id>(x);
		return in;
	}
	
	friend std::ostream& operator<<(std::ostream& out, const modint& num) {
		return out << num.val();
	}

public:
	static std::vector<modint> fact, inv_fact, invs;
 
private:
	int v;
	static int md;
};

template<int id> int modint<id>::md = 998244353;
template<int id> std::vector<modint<id>> modint<id>::fact = {1};
template<int id> std::vector<modint<id>> modint<id>::inv_fact = {1};
template<int id> std::vector<modint<id>> modint<id>::invs = {0};

using modint998244353 = modint<998244353>;
using modint1000000007 = modint<1000000007>;

namespace internal {

template<class T> struct is_modint : public std::false_type {};
template<int id> struct is_modint<modint<id>> : public std::true_type {};

template<class T, class ENABLE = void> struct is_static_modint : public std::false_type {};
template<int id> struct is_static_modint<modint<id>, std::enable_if_t<(id > 0)>> : public std::true_type {};
template<class T> using is_static_modint_t = std::enable_if_t<is_static_modint<T>::value>;

template<class T, class ENABLE = void> struct is_dynamic_modint : public std::false_type {};
template<int id> struct is_dynamic_modint<modint<id>, std::enable_if_t<(id <= 0)>> : public std::true_type {};
template<class T> using is_dynamic_modint_t = std::enable_if_t<is_dynamic_modint<T>::value>;

} // namespace internal


} // namespace felix

#line 3 "library/convolution/ntt.hpp"
#include <array>

#line 4 "library/math/pow-mod.hpp"

namespace felix {

namespace internal {

template<class T>
constexpr T pow_mod_constexpr(T x, long long n, T m) {
	using U = safely_multipliable_t<T>;
	if(m == 1) {
		return 0;
	}
	U r = 1, y = safe_mod(x, m);
	while(n) {
		if(n & 1) {
			r = (r * y) % m;
		}
		y = (y * y) % m;
		n >>= 1;
	}
	return r;
}

} // namespace internal


} // namespace felix

#line 4 "library/math/primitive-root.hpp"

namespace felix {

namespace internal {

constexpr int primitive_root_constexpr(int m) {
	if(m == 998244353) return 3;
	if(m == 167772161) return 3;
	if(m == 469762049) return 3;
	if(m == 754974721) return 11;
	if(m == 2) return 1;
	int divs[20] = {};
	divs[0] = 2;
	int cnt = 1;
	int x = (m - 1) / 2;
	x >>= __builtin_ctz(x);
	for(int i = 3; 1LL * i * i <= x; i += 2) {
		if(x % i == 0) {
			divs[cnt++] = i;
			while(x % i == 0) {
				x /= i;
			}
		}
	}
	if(x > 1) {
		divs[cnt++] = x;
	}
	for(int g = 2;; g++) {
		bool ok = true;
		for(int i = 0; i < cnt; i++) {
			if(pow_mod_constexpr(g, (m - 1) / divs[i], m) == 1) {
				ok = false;
				break;
			}
		}
		if(ok) {
			return g;
		}
	}
	assert(false);
}

} // namespace internal

} // namespace felix
#line 11 "library/convolution/ntt.hpp"

namespace felix {

namespace internal {

template<int mod>
struct NTT_prepare {
	using mint = modint<mod>;

	static constexpr int primitive_root = primitive_root_constexpr(mod);
	static constexpr int level = __builtin_ctz(mod - 1);

	std::array<mint, level + 1> root, iroot;
	std::array<mint, std::max(0, level - 2 + 1)> rate2, irate2;
	std::array<mint, std::max(0, level - 3 + 1)> rate3, irate3;

	constexpr NTT_prepare() {
		root[level] = mint(primitive_root).pow((mod - 1) >> level);
		iroot[level] = root[level].inv();
		for(int i = level - 1; i >= 0; i--) {
			root[i] = root[i + 1] * root[i + 1];
			iroot[i] = iroot[i + 1] * iroot[i + 1];
		}
		{
			mint prod = 1, iprod = 1;
			for(int i = 0; i <= level - 2; i++) {
				rate2[i] = root[i + 2] * prod;
				irate2[i] = iroot[i + 2] * iprod;
				prod *= iroot[i + 2];
				iprod *= root[i + 2];
			}
		}
		{
			mint prod = 1, iprod = 1;
			for(int i = 0; i <= level - 3; i++) {
				rate3[i] = root[i + 3] * prod;
				irate3[i] = iroot[i + 3] * iprod;
				prod *= iroot[i + 3];
				iprod *= root[i + 3];
			}
		}
	}
};

template<int mod>
struct NTT {
	using mint = modint<mod>;

	static NTT_prepare<mod> info;

	static void NTT4(std::vector<mint>& a) {
		int n = (int) a.size();
		int h = __builtin_ctz(n);
		int len = 0;
		while(len < h) {
			if(h - len == 1) {
				int p = 1 << (h - len - 1);
				mint rot = 1;
				for(int s = 0; s < (1 << len); s++) {
					int offset = s << (h - len);
					for(int i = 0; i < p; i++) {
						auto l = a[i + offset];
						auto r = a[i + offset + p] * rot;
						a[i + offset] = l + r;
						a[i + offset + p] = l - r;
					}
					if(s + 1 != (1 << len)) {
						rot *= info.rate2[__builtin_ctz(~(unsigned int) s)];
					}
				}
				len++;
			} else {
				int p = 1 << (h - len - 2);
				mint rot = 1, imag = info.root[2];
				for(int s = 0; s < (1 << len); s++) {
					mint rot2 = rot * rot;
					mint rot3 = rot2 * rot;
					int offset = s << (h - len);
					for(int i = 0; i < p; i++) {
						auto mod2 = 1ULL * mod * mod;
						auto a0 = 1ULL * a[i + offset].val();
						auto a1 = 1ULL * a[i + offset + p].val() * rot.val();
						auto a2 = 1ULL * a[i + offset + 2 * p].val() * rot2.val();
						auto a3 = 1ULL * a[i + offset + 3 * p].val() * rot3.val();
						auto a1na3imag = 1ULL * mint(a1 + mod2 - a3).val() * imag.val();
						auto na2 = mod2 - a2;
						a[i + offset] = a0 + a2 + a1 + a3;
						a[i + offset + 1 * p] = a0 + a2 + (2 * mod2 - (a1 + a3));
						a[i + offset + 2 * p] = a0 + na2 + a1na3imag;
						a[i + offset + 3 * p] = a0 + na2 + (mod2 - a1na3imag);
					}
					if(s + 1 != (1 << len))
						rot *= info.rate3[__builtin_ctz(~(unsigned int) s)];
				}
				len += 2;
			}
		}
	}

	static void iNTT4(std::vector<mint>& a) {
		int n = (int) a.size();
		int h = __builtin_ctz(n);
		int len = h;
		while(len) {
			if(len == 1) {
				int p = 1 << (h - len);
				mint irot = 1;
				for(int s = 0; s < (1 << (len - 1)); s++) {
					int offset = s << (h - len + 1);
					for(int i = 0; i < p; i++) {
						auto l = a[i + offset];
						auto r = a[i + offset + p];
						a[i + offset] = l + r;
						a[i + offset + p] = 1ULL * (mod + l.val() - r.val()) * irot.val();
					}
					if(s + 1 != (1 << (len - 1))) {
						irot *= info.irate2[__builtin_ctz(~(unsigned int) s)];
					}
				}
				len--;
			} else {
				int p = 1 << (h - len);
				mint irot = 1, iimag = info.iroot[2];
				for(int s = 0; s < (1 << (len - 2)); s++) {
					mint irot2 = irot * irot;
					mint irot3 = irot2 * irot;
					int offset = s << (h - len + 2);
					for(int i = 0; i < p; i++) {
						auto a0 = 1ULL * a[i + offset + 0 * p].val();
						auto a1 = 1ULL * a[i + offset + 1 * p].val();
						auto a2 = 1ULL * a[i + offset + 2 * p].val();
						auto a3 = 1ULL * a[i + offset + 3 * p].val();
						auto a2na3iimag = 1ULL * mint((mod + a2 - a3) * iimag.val()).val();
						a[i + offset] = a0 + a1 + a2 + a3;
						a[i + offset + 1 * p] = (a0 + (mod - a1) + a2na3iimag) * irot.val();
						a[i + offset + 2 * p] = (a0 + a1 + (mod - a2) + (mod - a3)) * irot2.val();
						a[i + offset + 3 * p] = (a0 + (mod - a1) + (mod - a2na3iimag)) * irot3.val();
					}
					if(s + 1 != (1 << (len - 2))) {
						irot *= info.irate3[__builtin_ctz(~(unsigned int) s)];
					}
				}
				len -= 2;
			}
		}
	}
};

template<int mod> NTT_prepare<mod> NTT<mod>::info;

template<class T>
std::vector<T> convolution_naive(const std::vector<T>& a, const std::vector<T>& b) {
	int n = (int) a.size(), m = (int) b.size();
	std::vector<T> ans(n + m - 1);
	if(n >= m) {
		for(int i = 0; i < n; i++) {
			for(int j = 0; j < m; j++) {
				ans[i + j] += a[i] * b[j];
			}
		}
	} else {
		for(int j = 0; j < m; j++) {
			for(int i = 0; i < n; i++) {
				ans[i + j] += a[i] * b[j];
			}
		}
	}
	return ans;
}

template<class mint, internal::is_static_modint_t<mint>* = nullptr>
std::vector<mint> convolution_ntt(std::vector<mint> a, std::vector<mint> b) {
	int n = (int) a.size(), m = (int) b.size();
	int sz = 1 << std::__lg(2 * (n + m - 1) - 1);
	a.resize(sz);
	b.resize(sz);
	NTT<mint::mod()>::NTT4(a);
	NTT<mint::mod()>::NTT4(b);
	for(int i = 0; i < sz; i++) {
		a[i] *= b[i];
	}
	NTT<mint::mod()>::iNTT4(a);
	a.resize(n + m - 1);
	mint iz = mint(sz).inv();
	for(int i = 0; i < n + m - 1; i++) {
		a[i] *= iz;
	}
	return a;
}

} // namespace internal


template<class mint, internal::is_static_modint_t<mint>* = nullptr>
std::vector<mint> convolution(std::vector<mint>&& a, std::vector<mint>&& b) {
	int n = (int) a.size(), m = (int) b.size();
	if(n == 0 || m == 0) {
		return {};
	}
	int sz = 1 << std::__lg(2 * (n + m - 1) - 1);
	assert((mint::mod() - 1) % sz == 0);
	if(std::min(n, m) < 128) {
		return internal::convolution_naive(a, b);
	}
	return internal::convolution_ntt(a, b);
}

template<class mint, internal::is_static_modint_t<mint>* = nullptr>
std::vector<mint> convolution(const std::vector<mint>& a, const std::vector<mint>& b) {
	int n = (int) a.size(), m = (int) b.size();
	if(n == 0 || m == 0) {
		return {};
	}
	int sz = 1 << std::__lg(2 * (n + m - 1) - 1);
	assert((mint::mod() - 1) % sz == 0);
	if(std::min(n, m) < 128) {
		return internal::convolution_naive(a, b);
	}
	return internal::convolution_ntt(a, b);
}

template<int mod, class T, std::enable_if_t<internal::is_integral<T>::value>* = nullptr>
std::vector<T> convolution(const std::vector<T>& a, const std::vector<T>& b) {
	using mint = modint<mod>;

	int n = (int) a.size(), m = (int) b.size();
	if(n == 0 || m == 0) {
		return {};
	}
	int sz = 1 << std::__lg(2 * (n + m - 1) - 1);
	assert((mod - 1) % sz == 0);
	std::vector<mint> a2(a.begin(), a.end());
	std::vector<mint> b2(b.begin(), b.end());
	auto c2 = convolution(std::move(a2), std::move(b2));
	std::vector<T> c(n + m - 1);
	for(int i = 0; i < n + m - 1; i++) {
		c[i] = c2[i].val();
	}
	return c;
}

template<class T>
std::vector<__uint128_t> convolution_u128(const std::vector<T>& a, const std::vector<T>& b) {
	static constexpr int m0 = 167772161; // 2^25

	static constexpr int m1 = 469762049; // 2^26

	static constexpr int m2 = 754974721; // 2^24

	static constexpr int r01 = internal::inv_gcd(m0, m1).second;
	static constexpr int r02 = internal::inv_gcd(m0, m2).second;
	static constexpr int r12 = internal::inv_gcd(m1, m2).second;
	static constexpr int r02r12 = 1LL * r02 * r12 % m2;
	static constexpr long long w1 = m0;
	static constexpr long long w2 = 1LL * m0 * m1;

	int n = (int) a.size(), m = (int) b.size();
	if(n == 0 || m == 0) {
		return {};
	}
	std::vector<__uint128_t> c(n + m - 1);
	if(std::min(n, m) < 128) {
		std::vector<__uint128_t> a2(a.begin(), a.end());
		std::vector<__uint128_t> b2(b.begin(), b.end());
		return internal::convolution_naive(a2, b2);
	}

	static constexpr int MAX_AB_BIT = 24;
	static_assert(m0 % (1ULL << MAX_AB_BIT) == 1, "m0 isn't enough to support an array length of 2^24.");
	static_assert(m1 % (1ULL << MAX_AB_BIT) == 1, "m1 isn't enough to support an array length of 2^24.");
	static_assert(m2 % (1ULL << MAX_AB_BIT) == 1, "m2 isn't enough to support an array length of 2^24.");
	assert(n + m - 1 <= (1 << MAX_AB_BIT));

	auto c0 = convolution<m0>(a, b);
	auto c1 = convolution<m1>(a, b);
	auto c2 = convolution<m2>(a, b);
	for(int i = 0; i < n + m - 1; i++) {
		long long n1 = c1[i], n2 = c2[i];
		long long x = c0[i];
		long long y = (n1 + m1 - x) * r01 % m1;
		long long z = ((n2 + m2 - x) * r02r12 + (m2 - y) * r12) % m2;
		c[i] = x + y * w1 + __uint128_t(z) * w2;
	}
	return c;
}

template<class mint, internal::is_static_modint_t<mint>* = nullptr>
std::vector<mint> convolution_large(const std::vector<mint>& a, const std::vector<mint>& b) {
	static constexpr int max_size = (mint::mod() - 1) & -(mint::mod() - 1);
	static constexpr int half_size = max_size >> 1;
	static constexpr int inv_max_size = internal::inv_gcd(max_size, mint::mod()).second;

	const int n = (int) a.size(), m = (int) b.size();
	if(n == 0 || m == 0) {
		return {};
	}
	if(std::min(n, m) < 128 || n + m - 1 <= max_size) {
		return internal::convolution_naive(a, b);
	}
	const int dn = (n + half_size - 1) / half_size;
	const int dm = (m + half_size - 1) / half_size;
	std::vector<std::vector<mint>> as(dn), bs(dm);
	for(int i = 0; i < dn; ++i) {
		const int offset = half_size * i;
		as[i] = std::vector<mint>(a.begin() + offset, a.begin() + std::min(n, offset + half_size));
		as[i].resize(max_size);
		internal::NTT<mint::mod()>::NTT4(as[i]);
	}
	for(int j = 0; j < dm; ++j) {
		const int offset = half_size * j;
		bs[j] = std::vector<mint>(b.begin() + offset, b.begin() + std::min(m, offset + half_size));
		bs[j].resize(max_size);
		internal::NTT<mint::mod()>::NTT4(bs[j]);
	}
	std::vector<std::vector<mint>> cs(dn + dm - 1, std::vector<mint>(max_size));
	for(int i = 0; i < dn; ++i) {
		for(int j = 0; j < dm; ++j) {
			for(int k = 0; k < max_size; ++k) {
				cs[i + j][k] += as[i][k] * bs[j][k];
			}
		}
	}
	std::vector<mint> c(n + m - 1);
	for(int i = 0; i < dn + dm - 1; ++i) {
		internal::NTT<mint::mod()>::iNTT4(cs[i]);
		const int offset = half_size * i;
		const int jmax = std::min(n + m - 1 - offset, max_size);
		for(int j = 0; j < jmax; ++j) {
			c[offset + j] += cs[i][j] * inv_max_size;
		}
	}
	return c;
}

} // namespace felix

#line 9 "library/formal-power-series/poly.hpp"

namespace felix {

template<int mod>
struct Poly {
	using mint = modint<mod>;

public:
	Poly() {}
	explicit Poly(int n) : a(n) {}
	explicit Poly(const std::vector<mint>& a) : a(a) {}
	Poly(const std::initializer_list<mint>& a) : a(a) {}

	template<class F>
	explicit Poly(int n, F f) : a(n) {
		for(int i = 0; i < n; i++) {
			a[i] = f(i);
		}
	}

	constexpr int size() const { return (int) a.size(); }
	constexpr void resize(int n) { a.resize(n); }

	constexpr void shrink() {
		while(size() && a.back() == 0) {
			a.pop_back();
		}
	}

	constexpr mint at(int idx) const {
		if(idx >= 0 && idx < size()) {
			return a[idx];
		} else {
			return 0;
		}
	}

	constexpr mint& operator[](int idx) { return a[idx]; }

	constexpr friend Poly operator+(const Poly& a, const Poly& b) {
		Poly c(std::max(a.size(), b.size()));
		for(int i = 0; i < c.size(); i++) {
			c[i] = a.at(i) + b.at(i);
		}
		return c;
	}

	constexpr friend Poly operator-(const Poly& a, const Poly& b) {
		Poly c(std::max(a.size(), b.size()));
		for(int i = 0; i < c.size(); i++) {
			c[i] = a.at(i) - b.at(i);
		}
		return c;
	}

	constexpr friend Poly operator*(Poly a, Poly b) {
		return Poly(convolution(a.a, b.a));
	}

	constexpr friend Poly operator*(mint a, Poly b) {
		for(int i = 0; i < b.size(); i++) {
			b[i] *= a;
		}
		return b;
	}

	constexpr friend Poly operator*(Poly a, mint b) {
		for(int i = 0; i < a.size(); i++) {
			a[i] *= b;
		}
		return a;
	}

	constexpr Poly& operator+=(Poly b) { return (*this) = (*this) + b; }
	constexpr Poly& operator-=(Poly b) { return (*this) = (*this) - b; }
	constexpr Poly& operator*=(Poly b) { return (*this) = (*this) * b; }
	constexpr Poly& operator*=(mint b) { return (*this) = (*this) * b; }
	
	constexpr Poly mulxk(int k) const {
		auto b = a;
		b.insert(b.begin(), k, mint(0));
		return Poly(b);
	}

	constexpr Poly modxk(int k) const {
		k = std::min(k, size());
		return Poly(std::vector<mint>(a.begin(), a.begin() + k));
	}

	constexpr Poly divxk(int k) const {
		if(size() <= k) {
			return Poly();
		}
		return Poly(std::vector<mint>(a.begin() + k, a.end()));
	}

	constexpr Poly deriv() const {
		if(a.empty()) {
			return Poly();
		}
		Poly c(size() - 1);
		for(int i = 0; i < size() - 1; ++i) {
			c[i] = (i + 1) * a[i + 1];
		}
		return c;
	}

	constexpr Poly integr() const {
		Poly c(size() + 1);
		mint::prepare(size());
		for(int i = 0; i < size(); ++i) {
			c[i + 1] = a[i] / mint(i + 1);
		}
		return c;
	}

	constexpr Poly inv(int m = -1) const {
		if(m == -1) {
			m = size();
		}
		Poly x{a[0].inv()};
		int k = 1;
		while(k < m) {
			k *= 2;
			x = (x * (Poly{mint(2)} - modxk(k) * x)).modxk(k);
		}
		return x.modxk(m);
	}

	constexpr Poly log(int m = -1) const {
		if(m == -1) {
			m = size();
		}
		return (deriv() * inv(m)).integr().modxk(m);
	}

	constexpr Poly exp(int m = -1) const {
		if(m == -1) {
			m = size();
		}
		Poly x{mint(1)};
		int k = 1;
		while(k < m) {
			k *= 2;
			x = (x * (Poly{mint(1)} - x.log(k) + modxk(k))).modxk(k);
		}
		return x.modxk(m);
	}

	constexpr Poly pow(long long k, int m = -1) const {
		if(m == -1) {
			m = size();
		}
		if(k == 0) {
			Poly b(m);
			b[0] = 1;
			return b;
		}
		int s = 0, sz = size();
		while(s < sz && a[s].val() == 0) {
			s++;
		}
		if(s == sz) {
			return *this;
		}
		if(m > 0 && s >= (sz + k - 1) / k) {
			return Poly(m);
		}
		if(s * k >= m) {
			return Poly(m);
		}
		return (((divxk(s) * a[s].inv()).log(m) * mint(k)).exp(m) * a[s].pow(k)).mulxk(s * k).modxk(m);
	}

	constexpr bool has_sqrt() const {
		if(size() == 0) {
			return true;
		}
		int x = 0;
		while(x < size() && a[x].val() == 0) {
			x++;
		}
		if(x == size()) {
			return true;
		}
		if(x % 2 == 1) {
			return false;
		}
		mint y = a[x];
		return (y == 0 || y.pow((mod - 1) / 2) == 1);
	}

	constexpr Poly sqrt(int m = -1) const {
		if(m == -1) {
			m = size();
		}
		if(size() == 0) {
			return Poly();
		}
		int x = 0;
		while(x < size() && a[x].val() == 0) {
			x++;
		}
		if(x == size()) {
			return Poly(size());
		}
		Poly f = divxk(x);
		Poly g({mint(f[0]).sqrt()});
		mint inv2 = mint(1) / 2;
		for(int i = 1; i < m; i *= 2) {
			g = (g + f.modxk(i * 2) * g.inv(i * 2)) * inv2;
		}
		return g.modxk(m).mulxk(x / 2);
	}

	constexpr Poly shift(mint c) const {
		int n = size();
		mint::prepare(n);
		Poly b(*this);
		for(int i = 0; i < n; i++) {
			b[i] *= mint::fact[i];
		}
		std::reverse(b.a.begin(), b.a.end());
		Poly exp_cx(std::vector<mint>(n, mint(1)));
		for(int i = 1; i < n; i++) {
			exp_cx[i] = exp_cx[i - 1] * c / i;
		}
		b = (b * exp_cx).modxk(n);
		std::reverse(b.a.begin(), b.a.end());
		for(int i = 0; i < n; i++) {
			b[i] *= mint::inv_fact[i];
		}
		return b;
	}

	constexpr Poly mulT(Poly b) const {
		if(b.size() == 0) {
			return Poly();
		}
		int n = b.size();
		std::reverse(b.a.begin(), b.a.end());
		return ((*this) * b).divxk(n - 1);
	}

	std::vector<mint> eval(std::vector<mint> x) const {
		if(size() == 0) {
			return std::vector<mint>(x.size(), mint(0));
		}
		const int n = std::max((int) x.size(), size());
		std::vector<Poly> q(4 * n);
		std::vector<mint> ans(x.size());
		x.resize(n);
		std::function<void(int, int, int)> build = [&](int p, int l, int r) {
			if(r - l == 1) {
				q[p] = Poly{1, -x[l]};
			} else {
				int m = (l + r) / 2;
				build(2 * p, l, m);
				build(2 * p + 1, m, r);
				q[p] = q[2 * p] * q[2 * p + 1];
			}
		};
		build(1, 0, n);
		std::function<void(int, int, int, const Poly&)> work = [&](int p, int l, int r, const Poly& num) {
			if(r - l == 1) {
				if(l < (int) ans.size()) {
					ans[l] = num.at(0);
				}
			} else {
				int m = (l + r) / 2;
				work(2 * p, l, m, num.mulT(q[2 * p + 1]).modxk(m - l));
				work(2 * p + 1, m, r, num.mulT(q[2 * p]).modxk(r - m));
			}
		};
		work(1, 0, n, mulT(q[1].inv(n)));
		return ans;
	}

private:
	std::vector<mint> a;
};

} // namespace felix

#line 6 "test/formal-power-series/poly/yosupo-Product-of-Polynomial-Sequence.test.cpp"
using namespace std;
using namespace felix;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin >> n;
	if(n == 0) {
		cout << "1\n";
		return 0;
	}
	vector<Poly<998244353>> a(n);
	for(int i = 0; i < n; i++) {
		int m;
		cin >> m;
		a[i].resize(m + 1);
		for(int j = 0; j <= m; j++) {
			cin >> a[i][j];
		}
	}
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
	for(int i = 0; i < n; i++) {
		pq.emplace(a[i].size(), i);
	}
	while(pq.size() > 1) {
		auto [x, i] = pq.top();
		pq.pop();
		auto [y, j] = pq.top();
		pq.pop();
		a[i] *= a[j];
		pq.emplace(a[i].size(), i);
	}
	auto id = pq.top().second;
	for(int i = 0; i < a[id].size(); i++) {
		cout << a[id][i] << " \n"[i == a[id].size() - 1];
	}
	return 0;
}
Back to top page