Whole rows and columns of combinatorial sequences in (Stirling, Bell, Bernoulli, partitions, power sums), Euler / set transforms (unlabelled multisets and sets: subset-sum counting, bounded knapsack counts), arithmetic on sparse series, and sums of exponential times polynomial for . Built on Power Series, taylorShift from Composition and Taylor Shift, lagrangeConsecutive from Evaluation and Interpolation and Comb (factorials up to the size used). The tables and the combinatorial meaning are in Stirling, Bell and Partitions.
| Sequence | Generating function | Computed as |
|---|---|---|
| unsigned , row | doubling | |
| , row | one convolution | |
| column : , | EGF , | one power |
| Bell | EGF | one exp |
| Bernoulli () | EGF | one inverse |
| partitions | = inverse of the pentagonal series | one inverse |
| power sums , all | EGF | one inverse |
| multisets of objects with counts (Euler transform) | harmonic sum + exp | |
| sets (distinct objects) | harmonic sum + exp |
Poly stirling1Row(int n) { // c(n, k), k = 0..n
if (n == 0) return {1};
Poly half = stirling1Row(n / 2), f = polyMul(half, taylorShift(half, mint(n / 2)));
f.resize(2 * (n / 2) + 1);
if (n % 2) { Poly g(n + 1, 0); for (int i = 0; i < sz(f); i++) g[i] += f[i] * mint(n - 1), g[i + 1] += f[i]; f = g; } // times (x + n - 1)
return f;
}
Poly stirling2Row(int n, const Comb& cb) { // S(n, k), k = 0..n
Poly a(n + 1), b(n + 1);
for (int i = 0; i <= n; i++) a[i] = mint(i).pow(n) * cb.fi[i], b[i] = i % 2 ? -cb.fi[i] : cb.fi[i];
Poly c = polyMul(a, b); c.resize(n + 1); return c;
}
Poly stirlingColumn(int k, int N, bool first, const Comb& cb) { // S(i, k) or c(i, k), i = 0..N
Poly res(N + 1, 0); if (k > N) return res;
int m = N - k + 1; Poly g(m);
for (int j = 0; j < m; j++) g[j] = first ? cb.iv[j + 1] : cb.fi[j + 1]; // (-ln(1-x))/x or (e^x - 1)/x
Poly gk = polyPow(g, k, m);
for (int i = k; i <= N; i++) res[i] = gk[i - k] * cb.f[i] * cb.fi[k];
return res;
}
Poly bellSeries(int N, const Comb& cb) {
Poly e(N + 1, 0); for (int i = 1; i <= N; i++) e[i] = cb.fi[i];
Poly b = polyExp(e, N + 1); for (int i = 0; i <= N; i++) b[i] *= cb.f[i];
return b;
}
Poly bernoulli(int N, const Comb& cb) { // cb covers N + 1
Poly g(N + 1); for (int i = 0; i <= N; i++) g[i] = cb.fi[i + 1];
Poly b = polyInv(g, N + 1); for (int i = 0; i <= N; i++) b[i] *= cb.f[i];
return b;
}
Poly partitionSeries(int N) {
Poly q(N + 1, 0);
for (int k = 0;; k++) { // generalised pentagonal numbers for k, -k
bool any = false;
for (int s : {k, -k}) { if (k == 0 && s < 0) continue; ll e = (ll)s * (3 * s - 1) / 2; if (e > N) continue; any = true; q[e] = k % 2 ? mint(-1) : mint(1); }
if (!any) break;
}
return polyInv(q, N + 1);
}
Poly powerSums(ll n, int K, const Comb& cb) { // ps[k] = sum_{i<n} i^k, k = 0..K (0^0 = 1); cb covers K + 1
Poly num(K + 1), den(K + 1); mint nn = mint(n), p = nn;
for (int i = 0; i <= K; i++) num[i] = p * cb.fi[i + 1], den[i] = cb.fi[i + 1], p *= nn;
Poly s = mulTrunc(num, polyInv(den, K + 1), K + 1);
for (int i = 0; i <= K; i++) s[i] *= cb.f[i];
return s;
}
Poly eulerTransform(const vector<mint>& a, int N, bool distinct) { // a[k] objects of size k (k >= 1); multisets or sets, sizes 0..N
vector<mint> iv(N + 2, 1); for (int i = 2; i <= N + 1; i++) iv[i] = -mint(998244353 / i) * iv[998244353 % i];
Poly lg(N + 1, 0);
for (int k = 1; k < sz(a) && k <= N; k++) if (a[k] != mint(0))
for (int j = 1; (ll)k * j <= N; j++) { mint t = a[k] * iv[j]; lg[k * j] += distinct && j % 2 == 0 ? -t : t; }
return polyExp(lg, N + 1);
}
Poly subsetSumCounts(const vector<int>& a, int t) { // #subsets with each sum 0..t (a_i >= 1)
vector<mint> cnt(t + 1, 0); for (int x : a) if (x <= t) cnt[x] += 1;
return eulerTransform(cnt, t, true);
}
Poly boundedKnapsackCounts(const vector<int>& cnt, const vector<int>& lim, int t) { // cnt[v] kinds of value v, each used 0..lim[v] times
vector<mint> iv(t + 2, 1); for (int i = 2; i <= t + 1; i++) iv[i] = -mint(998244353 / i) * iv[998244353 % i];
Poly lg(t + 1, 0);
for (int v = 1; v < sz(cnt) && v <= t; v++) {
if (!cnt[v]) continue;
for (int j = 1; (ll)v * j <= t; j++) lg[v * j] += mint(cnt[v]) * iv[j]; // -log(1 - x^v)
ll w = (ll)v * (lim[v] + 1);
for (int j = 1; w * j <= t; j++) lg[w * j] -= mint(cnt[v]) * iv[j]; // + log(1 - x^w)
}
return polyExp(lg, t + 1);
}Proof: the counting series
Stirling first kind: and , a Taylor shift; an odd multiplies by . Each level costs and the sizes halve: total. Second kind: inclusion–exclusion is the stated convolution. Columns: a set partition into blocks is a set of non-empty sets: EGF ; a permutation with cycles is a set of cycles, EGF of one cycle . Factoring out keeps the power well defined. Bell: a partition is a set of non-empty blocks, . Bernoulli: defined by , and . Partitions: Euler’s pentagonal theorem (proof in Stirling, Bell and Partitions). Power sums: , and of it is ; both numerator and denominator are divided by . Euler / set transform: and ; the harmonic double loop has terms. Bounded knapsack: . Each function was checked against the tables, brute-force subset enumeration and direct power sums.
Sparse series
When has only non-zero terms, , , , and cost without NTT (any prime modulus): each solves a first-order linear differential equation coefficient by coefficient (; ; ; ). For , factor out the lowest term : , zero if ; is with a Tonelli–Shanks root of and needs even (Library Checker ”… (Sparse)” problems).
using Sparse = vector<pair<int, mint>>; // (exponent, coefficient)
Sparse normalizeSparse(Sparse f) { // sort, merge equal exponents, drop zeros
sort(all(f), [](auto& a, auto& b) { return a.first < b.first; }); Sparse r;
for (auto [e, c] : f) { if (!r.empty() && r.back().first == e) r.back().second += c; else r.push_back({e, c}); }
Sparse out; for (auto& pr : r) if (pr.second != mint(0)) out.push_back(pr);
return out;
}
vector<mint> invTable(int n) { vector<mint> iv(n + 1, 1); for (int i = 2; i <= n; i++) iv[i] = -mint(998244353 / i) * iv[998244353 % i]; return iv; }
vector<mint> sparseInv(const Sparse& f, int n) { // f_0 != 0
mint f0 = 0; for (auto [i, c] : f) if (i == 0) f0 += c;
mint i0 = f0.inv(); vector<mint> g(n, 0);
for (int k = 0; k < n; k++) { mint s = k == 0 ? mint(1) : mint(0); for (auto [i, c] : f) if (i >= 1 && i <= k) s -= c * g[k - i]; g[k] = s * i0; }
return g;
}
vector<mint> sparseExp(const Sparse& f, int n) { // f_0 = 0: k g_k = sum i f_i g_{k-i}
vector<mint> g(n, 0), iv = invTable(n); if (n) g[0] = 1;
for (int k = 1; k < n; k++) { mint s = 0; for (auto [i, c] : f) if (i >= 1 && i <= k) s += mint(i) * c * g[k - i]; g[k] = s * iv[k]; }
return g;
}
vector<mint> sparseLog(const Sparse& f, int n) { // f_0 = 1: k g_k = k f_k - sum_{i>=1} f_i (k - i) g_{k-i}
vector<mint> g(n, 0), iv = invTable(n), fk(n, 0);
for (auto [i, c] : f) if (i < n) fk[i] += c;
for (int k = 1; k < n; k++) { mint s = mint(k) * fk[k]; for (auto [i, c] : f) if (i >= 1 && i < k) s -= c * mint(k - i) * g[k - i]; g[k] = s * iv[k]; }
return g;
}
vector<mint> sparsePowUnit(const Sparse& f, mint M, mint f0pow, int n) { // f_0 != 0, M a field element (also 1/2); g_0 = f0pow
mint f0 = 0; for (auto [i, c] : f) if (i == 0) f0 += c;
vector<mint> g(n, 0), iv = invTable(n); if (n) g[0] = f0pow; mint i0 = f0.inv();
for (int k = 1; k < n; k++) { // k f_0 g_k = sum_{i>=1} f_i (M i - (k - i)) g_{k-i}
mint s = 0; for (auto [i, c] : f) if (i >= 1 && i <= k) s += c * (M * mint(i) - mint(k - i)) * g[k - i];
g[k] = s * iv[k] * i0;
}
return g;
}
vector<mint> sparsePow(Sparse f, ll M, int n) { // f^M, any f, M >= 0
vector<mint> zero(n, 0);
if (M == 0) { if (n) zero[0] = 1; return zero; }
f = normalizeSparse(f);
if (f.empty()) return zero;
int s = f[0].first; if ((i128)s * M >= n) return zero;
mint c = f[0].second; for (auto& [i, x] : f) i -= s;
vector<mint> g = sparsePowUnit(f, mint(M % 998244353), c.pow(M), n - s * M);
g.insert(g.begin(), s * M, mint(0)); return g;
}
bool sparseSqrt(Sparse f, int n, vector<mint>& out) { // false if no square root
f = normalizeSparse(f);
out.assign(n, 0);
if (f.empty()) return true;
int s = f[0].first; if (s >= n) return true; // 0 works modulo x^n
if (s % 2) return false;
mint r; if (!sqrtMint(f[0].second, r)) return false;
for (auto& [i, x] : f) i -= s;
vector<mint> g = sparsePowUnit(f, mint(2).inv(), r, n - s / 2);
for (int i = 0; i + s / 2 < n; i++) out[i + s / 2] = g[i];
return true;
}Proof: the sparse recurrences
Compare coefficients of in each differential equation. Inverse: gives . Exp: satisfies : . Log: satisfies : , isolate . Power: satisfies : , giving . This recurrence has a unique solution with prescribed , and for , the true power satisfies it, also for (then since and both have constant term 1, so has zero derivative and equals 1). Scaling by : . The exponent enters the recurrence only modulo , while uses the true . Each coefficient touches terms: . Verified against the dense NTT versions.
Sums of exponential times polynomial
for a polynomial of degree given by and (Library Checker “Sum of Exponential times Polynomial”, and its “Limit” version for ), plus the binomial basis with (Newton forward differences). For the sum is a polynomial of degree (interpolate); for , with , , , and Lagrange gives .
mint sumExpTimesPoly(vector<mint> fv, mint a, ll n, const Comb& cb) { // sum_{i<n} a^i f(i), fv = f(0..d); cb covers d + 3
if (n <= 0) return 0;
int d = sz(fv) - 1; fv.push_back(lagrangeConsecutive(fv, d + 1, cb)); // f(d + 1)
vector<mint> S(d + 2); mint ap = 1;
for (int i = 0; i <= d + 1; i++) S[i] = (i ? S[i - 1] : mint(0)) + ap * fv[i], ap *= a;
ll N = n - 1;
if (N <= d + 1) return S[N];
if (a == mint(0)) return fv[0];
if (a == mint(1)) return lagrangeConsecutive(S, N, cb);
mint c = 0, ak = 1;
for (int i = d + 1; i >= 0; i--) { mint t = cb.C(d + 1, i) * ak * S[i]; c += (d + 1 - i) % 2 ? -t : t; ak *= a; }
c /= (mint(1) - a).pow(d + 1);
vector<mint> g(d + 1); mint ainv = a.inv(), aip = 1;
for (int i = 0; i <= d; i++) g[i] = (S[i] - c) * aip, aip *= ainv;
return lagrangeConsecutive(g, N, cb) * a.pow(N) + c;
}
mint sumExpTimesPolyLimit(vector<mint> fv, mint a, const Comb& cb) { // sum_{i>=0} a^i f(i) as the formal limit, a != 1
if (fv.empty()) return 0;
int d = sz(fv) - 1; fv.push_back(lagrangeConsecutive(fv, d + 1, cb));
vector<mint> S(d + 2); mint ap = 1, s = 0;
for (int i = 0; i <= d + 1; i++) s += ap * fv[i], S[i] = s, ap *= a;
mint c = 0, ak = 1;
for (int i = d + 1; i >= 0; i--) { mint t = cb.C(d + 1, i) * ak * S[i]; c += (d + 1 - i) % 2 ? -t : t; ak *= a; }
return c / (mint(1) - a).pow(d + 1);
}
Poly toBinomialBasis(const vector<mint>& fv, const Comb& cb) { // c_k = k! sum_j f(j)/j! (-1)^(k-j)/(k-j)!
int n = sz(fv); Poly a(n), b(n);
for (int i = 0; i < n; i++) a[i] = fv[i] * cb.fi[i], b[i] = i % 2 ? -cb.fi[i] : cb.fi[i];
Poly c = polyMul(a, b); c.resize(n); for (int k = 0; k < n; k++) c[k] *= cb.f[k];
return c;
}
Poly fromBinomialBasis(const vector<mint>& c, const Comb& cb) { // f(i) = i! sum_k c_k/k! 1/(i-k)!
int n = sz(c); Poly a(n), b(n);
for (int i = 0; i < n; i++) a[i] = c[i] * cb.fi[i], b[i] = cb.fi[i];
Poly f = polyMul(a, b); f.resize(n); for (int i = 0; i < n; i++) f[i] *= cb.f[i];
return f;
}Proof: the structure and the binomial basis
We need a polynomial with , i.e. . On polynomials of degree the map is triangular with respect to degree with diagonal , hence bijective, so such exists and is constant: . Since , ; substituting and multiplying by : . The “limit” is this (the value of whenever it converges, ). For , is a polynomial of degree (finite differences). Binomial basis: is a basis of polynomials and , so , which is the convolution of with times ; the inverse map is the convolution with . Verified against direct summation (including ) and round trips.
Related notes
- Stirling, Bell and Partitions
- Generating Functions
- Knapsack (counting versions via exp / log)