多项式复合和拉格朗日反演 学习笔记

20220520

一些小东西

卡特兰数和去掉根的二叉树对应。考虑卡特兰数的递推公式 \(f(n) = \sum_{i = 0}^{n - 1} f(i) f(n - 1 - i)\),并有 \(f(0) = f(1) = 1\)

多项式复合

复合运算

幂级数的复合运算。

\(A(w)=\sum_{i\ge 0} a_i w^i\)\(B(x)=\sum_{i \ge 1}b_i x ^ i\)。则 \(B(x)\)\(A(w)\) 的复合就是

\[C(x) = (A\circ B)(x) = A(B(x))=\sum_{i \ge 0} a_i (B(x)) ^ i \]

可以将其整理为 \(c_i = \sum_{i \ge 0} c_i x ^ i\) 的形式。

由于假定 \(b_0 = 0\),于是即使 \(A\) 有无限多个非 \(0\) 项, \(B(x)\)\(A(w)\) 的复合可以定义。

多项式复合函数

给定 \(f(w), g(x)\),如果直接用定义计算 \(f(g(x))\) 的前 \(n\) 项系数,时间复杂度为一个常数大的 \(O(n ^ 2\log n)\)

下面是一个复杂度为 \(O(N ^ 2)\) 的算法,运用了大步小步的算法思想。

\(d = \lceil \sqrt n\rceil\),先求出来 \(g(x) ^ 2, g(x) ^ 3, \ldots , g(x) ^ d\),复杂度是 \(O(d n\log n)\),同时计算 \(g(x) ^ {2d},g(x)^{3d},\ldots ,g(x) ^ {d(d - 1)}\),复杂度为 \(O(d n\log n)\)

接下来,根据

\[f(x) = \sum_{i \ge 0}f_i (g(x) ^ i) = \sum_{0 \le i < d}g(x) ^ {id}\sum_{0\le j <d} f_{id + j} g(x)^j \]

然后发现,如果我暴力计算 \(\sum_{0\le j <d} f_{id + j} g(x)^j\) 然后再暴力把前面的式子加起来,一共的多项式加法次数不会超过 \(O(N)\),于是这部分复杂度为 \(O(N ^ 2)\)

如是说,我们在 \(O(N ^ 2 + n \sqrt n \log n)\) 的情况下计算了多项式复合逆。

题目:P5373 【模板】多项式复合函数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

// Siriqwq
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::vector;
using std::copy;
using std::reverse;
using std::sort;
using std::get;
using std::unique;
using std::swap;
using std::array;
using std::cerr;
using std::function;
using std::map;
using std::set;
using std::pair;
using std::mt19937;
using std::make_pair;
using std::tuple;
using std::make_tuple;
using std::uniform_int_distribution;
using ll = long long;
namespace qwq {
	mt19937 eng;
	void init(int Seed) {return eng.seed(Seed);}
	int rnd(int l = 1, int r = 1000000000) {return uniform_int_distribution<int> (l, r)(eng);}
}
template<typename T>
inline void chkmin(T &x, T y) {if (x > y) x = y;}
template<typename T>
inline void chkmax(T &x, T y) {if (x < y) x = y;}
template<typename T>
inline T min(const T &x, const T &y) {return x < y ? x : y;}
template<typename T>
inline T max(const T &x, const T &y) {return x > y ? x : y;}
char buf[100000], *bufs, *buft;
#define gc() ((bufs == buft && (buft = (bufs = buf) + fread(buf, 1, 100000, stdin))), bufs == buft ? -1 : *bufs++);
template<typename T>
inline void read(T &x) {
	x = 0;
	bool f = 0;
	char ch = gc();
	while (!isdigit(ch)) f = ch == '-', ch = gc();
	while (isdigit(ch)) x = x * 10 + ch - '0', ch = gc();
	if (f) x = -x;
}
inline void reads(char *s) {
	char ch = gc();
	while (isspace(ch)) ch = gc();
	while (!isspace(ch)) *(s++) = ch;
	return;
}
template<typename T, typename ...Arg>
inline void read(T &x, Arg &... y) {
	read(x);
	read(y...);
}
#define O(x) cerr << #x << " : " << x << '\n'
const double Pi = acos(-1);
const int MAXN = 262144, MOD = 998244353, inv2 = (MOD + 1) / 2, I32_INF = 0x3f3f3f3f;
const long long I64_INF = 0x3f3f3f3f3f3f3f3f;
auto Ksm = [] (int x, int y) -> int {
	if (y < 0) {
		y %= MOD - 1;
		y += MOD - 1;
	}
	int ret = 1;
	for (; y; y /= 2, x = (long long) x * x % MOD) if (y & 1) ret = (long long) ret * x % MOD;
	return ret;
};
auto Mod = [] (int x) -> int {
	if (x >= MOD) return x - MOD;
	else if (x < 0) return x + MOD;
	else return x;
};
template<const int N_num, const int M_num>
struct Graph {
	int H[N_num];
	struct Edge {int to, lac;} e[M_num];
	inline void add_edge(int x, int y) {e[*H] = {y, H[x]};H[x] = (*H)++;}
	inline void init() {memset(H, -1, sizeof H);*H = 0;}
};
#define go(x, y) for (int i = x.H[y], v; (v = x.e[i].to) && ~i; i = x.e[i].lac)
inline int ls(int k) {return k << 1;}
inline int rs(int k) {return k << 1 | 1;}
int fac[MAXN], ifac[MAXN];
namespace POLY {
int SZ, R[MAXN], W[MAXN], INV[MAXN + 1];
void POLYINIT() {
	INV[1] = 1;
	for (int i = 2; i <= MAXN; ++i) INV[i] = (long long) (MOD - MOD / i) * INV[MOD % i] % MOD;
}
void INIT(int len) {
	if (SZ == len) return;
	SZ = len;
	for (int i = 1; i < len; ++i) R[i] = (R[i >> 1] >> 1) | (i & 1 ? (len >> 1) : 0);
	int wn = Ksm(3, (MOD - 1) / len);
	W[len >> 1] = 1;
	for (int i = (len >> 1) + 1; i < len; ++i) W[i] = (long long) W[i - 1] * wn % MOD;
	for (int i = (len >> 1) - 1; i > 0; --i) W[i] = W[i << 1];
}
unsigned long long c[MAXN];
void Ntt(vector<int>& F, int limit, int type) {
	copy(F.begin(), F.begin() + limit, c);
	for (int i = 1; i < limit; ++i) if (i < R[i]) swap(c[i], c[R[i]]);
	for (int o = 2, j = 1; o <= limit; o <<= 1, j <<= 1) {
		for (int i = 0; i < limit; i += o) {
			for (int k = 0; k < j; ++k) {
				unsigned long long OI = c[i + j + k] * W[k + j] % MOD;
				c[i + j + k] = c[i + k] + MOD - OI;
				c[i + k] += OI;
			}
		}
	}
	if (type == -1) {
		reverse(c + 1, c + limit);
		int inv = INV[limit];
		for (int i = 0; i < limit; ++i) c[i] = c[i] % MOD * inv % MOD;
	}
	for (int i = 0; i < limit; ++i) F[i] = c[i] % MOD;
}
struct Poly {
	vector<int> v;
	int& operator [] (const int &pos) { return v[pos]; }
	int len() { return v.size(); }
	void set(int l) { return v.resize(l); }
	void adjust() { while (v.size() > 1 && !v.back()) v.pop_back(); }
	void rev() { reverse(v.begin(), v.end()); }
	void Ntt(int L, int type) {
		int limit = 1 << L;
		INIT(limit);
		set(limit);
		POLY::Ntt(v, limit, type);
	}
	void Squ() {
		int L = ceil(log2(len())) + 1, limit = 1 << L;
		Ntt(L, 1);
		for (int i = 0; i < limit; ++i) v[i] = (long long) v[i] * v[i] % MOD;
		Ntt(L, -1);
		adjust();
	}
	void operator += (Poly &x) {
		if (len() < x.len()) set(x.len());
		for (int i = 0; i < x.len(); ++i) v[i] = Mod(v[i] + x[i]);
		adjust();
	}
	void operator -= (Poly &x) {
		if (len() < x.len()) set(x.len());
		for (int i = 0; i < x.len(); ++i) v[i] = Mod(v[i] - x[i]);
		adjust();
	}
	Poly operator * (Poly &x) {
		Poly ret, tmp0 = *this, tmp1 = x;
		int L = ceil(log2(tmp0.len() + tmp1.len() - 1)), n = 1 << L;
		tmp0.Ntt(L, 1);
		tmp1.Ntt(L, 1);
		ret.set(n);
		for (int i = 0; i < n; ++i) ret[i] = (long long) tmp0[i] * tmp1[i] % MOD;
		ret.Ntt(L, -1);
		ret.adjust();
		return ret;
	}
	Poly operator - (Poly &x) {
		Poly ret;
		ret.set(max(len(), x.len()));
		for (int i = 0; i < len(); ++i) ret[i] = v[i];
		for (int i = 0; i < x.len(); ++i) ret[i] = Mod(ret[i] - x[i]);
		return ret;
	}
	Poly operator * (int x) {
		Poly ret = *this;
		for (auto &i: ret.v) i = (ll) i * x % MOD;
		return ret;
	}
	Poly operator + (Poly &x) {
		Poly ret;
		ret.set(max(len(), x.len()));
		for (int i = 0; i < len(); ++i) ret[i] = v[i];
		for (int i = 0; i < x.len(); ++i) ret[i] = Mod(ret[i] + x[i]);
		return ret;
	}
	void operator *= (Poly &x) {
		Poly tmp = x;
		int L = ceil(log2(len() + x.len() - 1)), n = 1 << L;
		Ntt(L, 1);
		tmp.Ntt(L, 1);
		for (int i = 0; i < n; ++i) v[i] = (long long) v[i] * tmp[i] % MOD;
		Ntt(L, -1);
		adjust();
	}
};
}
using namespace POLY;
int N, M;
Poly f, g, b1[150], b2[150];
int main() {
	// std::ios::sync_with_stdio(0);
	// cout << std::fixed << std::setprecision(8);
	// cin.tie(0);
	// cout.tie(0);
	freopen("1.in", "r", stdin);
	// freopen("1.out", "w", stdout);
	read(N);
	read(M);
	++N;
	++M;
	qwq::init(20050112);
	int lim = ceil(sqrt(N));
	POLYINIT();
	f.set(N);
	g.set(M);
	for (int i = 0; i < N; ++i) read(f[i]);
	for (int j = 0; j < M; ++j) read(g[j]);
	b1[0].set(N);
	b1[0][0] = 1;
	for (int i = 1; i < lim; ++i) b1[i] = b1[i - 1] * g, b1[i].set(N);
	Poly tmp = g * b1[lim - 1];
	tmp.set(N);
	b2[0].set(N);
	b2[0][0] = 1;
	for (int i = 1; i < lim; ++i) b2[i] = b2[i - 1] * tmp, b2[i].set(N);
	Poly ans;
	ans.set(N);
	for (int i = 0; i < lim; ++i) {
		Poly ret;
		ret.set(N);
		for (int j = 0; j < lim; ++j) {
			if (i * lim + j >= N) break;
			Poly tmp = b1[j] * f[i * lim + j];
			ret += tmp;
			// ret.set(N);
		}
		ret *= b2[i];
		ret.set(N);
		ans += ret;
	}
	ans.set(N);
	for (int i = 0; i < N; ++i) printf("%d ", ans[i]);
	puts("");
	// cout << (-3 / 2);
	cerr << ((double) clock() / CLOCKS_PER_SEC) << '\n';
	return (0-0);
}

多项式复合逆

给定 \(f(x)\),满足 \(f_0 = 0,f_1 \not = 0\),求 \(g(x)\),满足 \(f(g(x))=x\)

先来一个公式,就是下面的反演公式

\[[x^n] g(x) = \frac{1}{n}[w ^ {n - 1}]\left(\frac{w}{f(w)}\right)^n \]

然后,我们还是类似于上面的套路,利用大步小步思想,令 \(d=\lceil \sqrt n\rceil\)

\[\begin{aligned} g(x)&=\sum_{i = 1}^{n}\left(\frac{1}{i}\left(\frac{w}{f(w)}\right)^i[w ^ { ^{i - 1}}]\right) x ^ i \\ &=\sum_{i = 0}^{d - 1}\sum_{j = 1}^{d}[id + j \le n] \left(\left(\frac{1}{id + j} [w^{id + j - 1}]\left(\frac{w}{f(w)}\right)^{id} \left(\frac{w}{f(w)}\right)^{j}\right)\right)x ^ {id + j} \end{aligned} \]

通过预处理 \(\left(\frac{w}{f(w)}\right)\) 的若干次幂可以做到 \(O(N^2+N\sqrt N \log N)\) 的复杂度。

// Siriqwq
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::vector;
using std::copy;
using std::reverse;
using std::sort;
using std::get;
using std::unique;
using std::swap;
using std::array;
using std::cerr;
using std::function;
using std::map;
using std::set;
using std::pair;
using std::mt19937;
using std::make_pair;
using std::tuple;
using std::make_tuple;
using std::uniform_int_distribution;
using ll = long long;
namespace qwq {
	mt19937 eng;
	void init(int Seed) {return eng.seed(Seed);}
	int rnd(int l = 1, int r = 1000000000) {return uniform_int_distribution<int> (l, r)(eng);}
}
template<typename T>
inline void chkmin(T &x, T y) {if (x > y) x = y;}
template<typename T>
inline void chkmax(T &x, T y) {if (x < y) x = y;}
template<typename T>
inline T min(const T &x, const T &y) {return x < y ? x : y;}
template<typename T>
inline T max(const T &x, const T &y) {return x > y ? x : y;}
char buf[100000], *bufs, *buft;
#define gc() ((bufs == buft && (buft = (bufs = buf) + fread(buf, 1, 100000, stdin))), bufs == buft ? -1 : *bufs++);
template<typename T>
inline void read(T &x) {
	x = 0;
	bool f = 0;
	char ch = gc();
	while (!isdigit(ch)) f = ch == '-', ch = gc();
	while (isdigit(ch)) x = x * 10 + ch - '0', ch = gc();
	if (f) x = -x;
}
inline void reads(char *s) {
	char ch = gc();
	while (isspace(ch)) ch = gc();
	while (!isspace(ch)) *(s++) = ch;
	return;
}
template<typename T, typename ...Arg>
inline void read(T &x, Arg &... y) {
	read(x);
	read(y...);
}
#define O(x) cerr << #x << " : " << x << '\n'
const double Pi = acos(-1);
const int MAXN = 262144, MOD = 998244353, inv2 = (MOD + 1) / 2, I32_INF = 0x3f3f3f3f;
const long long I64_INF = 0x3f3f3f3f3f3f3f3f;
auto Ksm = [] (int x, int y) -> int {
	if (y < 0) {
		y %= MOD - 1;
		y += MOD - 1;
	}
	int ret = 1;
	for (; y; y /= 2, x = (long long) x * x % MOD) if (y & 1) ret = (long long) ret * x % MOD;
	return ret;
};
auto Mod = [] (int x) -> int {
	if (x >= MOD) return x - MOD;
	else if (x < 0) return x + MOD;
	else return x;
};
template<const int N_num, const int M_num>
struct Graph {
	int H[N_num];
	struct Edge {int to, lac;} e[M_num];
	inline void add_edge(int x, int y) {e[*H] = {y, H[x]};H[x] = (*H)++;}
	inline void init() {memset(H, -1, sizeof H);*H = 0;}
};
#define go(x, y) for (int i = x.H[y], v; (v = x.e[i].to) && ~i; i = x.e[i].lac)
inline int ls(int k) {return k << 1;}
inline int rs(int k) {return k << 1 | 1;}
int fac[MAXN], ifac[MAXN];
namespace POLY {
int SZ, R[MAXN], W[MAXN], INV[MAXN + 1];
void POLYINIT() {
	INV[1] = 1;
	for (int i = 2; i <= MAXN; ++i) INV[i] = (long long) (MOD - MOD / i) * INV[MOD % i] % MOD;
}
void INIT(int len) {
	if (SZ == len) return;
	SZ = len;
	for (int i = 1; i < len; ++i) R[i] = (R[i >> 1] >> 1) | (i & 1 ? (len >> 1) : 0);
	int wn = Ksm(3, (MOD - 1) / len);
	W[len >> 1] = 1;
	for (int i = (len >> 1) + 1; i < len; ++i) W[i] = (long long) W[i - 1] * wn % MOD;
	for (int i = (len >> 1) - 1; i > 0; --i) W[i] = W[i << 1];
}
unsigned long long c[MAXN];
void Ntt(vector<int>& F, int limit, int type) {
	copy(F.begin(), F.begin() + limit, c);
	for (int i = 1; i < limit; ++i) if (i < R[i]) swap(c[i], c[R[i]]);
	for (int o = 2, j = 1; o <= limit; o <<= 1, j <<= 1) {
		for (int i = 0; i < limit; i += o) {
			for (int k = 0; k < j; ++k) {
				unsigned long long OI = c[i + j + k] * W[k + j] % MOD;
				c[i + j + k] = c[i + k] + MOD - OI;
				c[i + k] += OI;
			}
		}
	}
	if (type == -1) {
		reverse(c + 1, c + limit);
		int inv = INV[limit];
		for (int i = 0; i < limit; ++i) c[i] = c[i] % MOD * inv % MOD;
	}
	for (int i = 0; i < limit; ++i) F[i] = c[i] % MOD;
}
struct Poly {
	vector<int> v;
	int& operator [] (const int &pos) { return v[pos]; }
	int len() { return v.size(); }
	void set(int l) { return v.resize(l); }
	void adjust() { while (v.size() > 1 && !v.back()) v.pop_back(); }
	void rev() { reverse(v.begin(), v.end()); }
	void Ntt(int L, int type) {
		int limit = 1 << L;
		INIT(limit);
		set(limit);
		POLY::Ntt(v, limit, type);
	}
	void Squ() {
		int L = ceil(log2(len())) + 1, limit = 1 << L;
		Ntt(L, 1);
		for (int i = 0; i < limit; ++i) v[i] = (long long) v[i] * v[i] % MOD;
		Ntt(L, -1);
		adjust();
	}
	void operator += (Poly &x) {
		if (len() < x.len()) set(x.len());
		for (int i = 0; i < x.len(); ++i) v[i] = Mod(v[i] + x[i]);
		adjust();
	}
	void operator -= (Poly &x) {
		if (len() < x.len()) set(x.len());
		for (int i = 0; i < x.len(); ++i) v[i] = Mod(v[i] - x[i]);
		adjust();
	}
	Poly operator * (Poly &x) {
		Poly ret, tmp0 = *this, tmp1 = x;
		int L = ceil(log2(tmp0.len() + tmp1.len() - 1)), n = 1 << L;
		tmp0.Ntt(L, 1);
		tmp1.Ntt(L, 1);
		ret.set(n);
		for (int i = 0; i < n; ++i) ret[i] = (long long) tmp0[i] * tmp1[i] % MOD;
		ret.Ntt(L, -1);
		ret.adjust();
		return ret;
	}
	Poly operator - (Poly &x) {
		Poly ret;
		ret.set(max(len(), x.len()));
		for (int i = 0; i < len(); ++i) ret[i] = v[i];
		for (int i = 0; i < x.len(); ++i) ret[i] = Mod(ret[i] - x[i]);
		return ret;
	}
	Poly operator * (int x) {
		Poly ret = *this;
		for (auto &i: ret.v) i = (ll) i * x % MOD;
		return ret;
	}
	Poly operator + (Poly &x) {
		Poly ret;
		ret.set(max(len(), x.len()));
		for (int i = 0; i < len(); ++i) ret[i] = v[i];
		for (int i = 0; i < x.len(); ++i) ret[i] = Mod(ret[i] + x[i]);
		return ret;
	}
	void operator *= (Poly &x) {
		Poly tmp = x;
		int L = ceil(log2(len() + x.len() - 1)), n = 1 << L;
		Ntt(L, 1);
		tmp.Ntt(L, 1);
		for (int i = 0; i < n; ++i) v[i] = (long long) v[i] * tmp[i] % MOD;
		Ntt(L, -1);
		adjust();
	}
	Poly GetInv(int deg = -1) {
		if (deg == 1) return {{Ksm(v[0], MOD - 2)}};
		Poly ret = GetInv((deg + 1) / 2), tmp;
		int L = ceil(log2(deg)) + 1, n = 1 << L, mx = min(len(), deg);
		tmp.set(deg);
		for (int i = 0; i < mx; ++i) tmp[i] = v[i];
		tmp.Ntt(L, 1);
		ret.Ntt(L, 1);
		for (int i = 0; i < n; ++i) ret[i] = (2 - (long long) tmp[i] * ret[i] % MOD + MOD) * ret[i] % MOD;
		ret.Ntt(L, -1);
		ret.set(deg);
		return ret;
	}
};
}
using namespace POLY;
Poly f, g, b1[132], b2[132];
int N;
int main() {
// 	freopen("1.in", "r", stdin);
	read(N);
	POLYINIT();
	f.set(N);
	read(f[0]);
	--N;
	for (int i = 0; i < N; ++i) read(f[i]);
	int d = ceil(sqrt(N));
	g = f.GetInv(N);
	b2[0].set(N);
	b2[0][0] = 1;
	for (int i = 1; i <= d; ++i) b2[i] = b2[i - 1] * g, b2[i].set(N);
	b1[0].set(N);
	b1[0][0] = 1;
	for (int i = 1; i <= d; ++i) b1[i] = b1[i - 1] * b2[d], b1[i].set(N);
	printf("0 ");
	for (int i = 0; i < d; ++i) {
		for (int j = 1; j <= d; ++j) {
			int nw = i * d + j - 1;
			if (nw >= N) {
				puts("");
				cerr << ((double) clock() / CLOCKS_PER_SEC) << '\n';
				return 0;
			}
			int ans = 0;
			for (int k = 0; k <= nw; ++k) ans = (ans + (ll) b1[i][k] * b2[j][nw - k]) % MOD;
			ans = (ll) ans * INV[nw + 1] % MOD;
			printf("%d ", ans);
		}
	}
	puts("");
	cerr << ((double) clock() / CLOCKS_PER_SEC) << '\n';
	return 0;
}

右复合一些特定幂级数

  • 复合 \(cx\),直接将 \(f_n = f_n c ^ n\) 即可。
  • 复合 \(x ^ a\),将 \(f_i\) 的系数挪到 \(f_{ia}\) 即可
  • 复合 \(x+c\)\(F(x + c) = \sum_{n\ge 0}f_n (x + c)^n = \sum_{n\ge 0}f_n\sum_{i = 0}^ n \binom{n}{i}x ^ i c ^ {n - i}= \sum_{i \ge 0} x ^ i \sum_{n \ge i} f_n c ^ {n - i}\) 。然后是一个差卷积的形式,可以通过翻转 \(c\) 转化为和卷积去做。
  • 复合 \(\sqrt {x + 1}\) ,不会。

CF438E

对于一棵有根无标号二叉树,对其每个结点赋予一个正整数权值,且在 \(\{c_1,c_2,\dots,c_n\}\) 中。
给定 \(m\),问权值为 \(0,1,\dots,m\) 的二叉树数量,模 998244353998244353。

\(n\) 个节点无标号二叉树生成函数是 \(C(x)\),其中它满足 $C(x) = x (C(x))^2+1 $

考虑对于一个节点定义 \(W(x) = \sum_{i = 1} ^ nx ^ {c_i}\),那么,我们就知道答案 \(F = C\circ W\)。那么应该有

\[F(W(x)) = W(x) F(W(x))^ 2+1 \]

把他写成

\[F =W F ^ 2 + 1 \]

\[F= \frac{1±\sqrt {1 - 4W} }{2W} \]

考虑在化一下,变成

\[F = \frac{2}{1± \sqrt{1 - 4 W}} \]

考虑如果取减号,下面式子不能求逆。所以是加号。

然后就可以做了,使用多项式开根和多项式求逆。

\(\text{Lagrange}\) 反演

\(\text{Lagrange}\) 反演 基础形式

若两个形式幂级数满足 \(f (g(x)) = x\) ,那么称 \(F\)\(G\) 的复合逆,不难发现两个幂级数互为复合逆

\[[x ^ n] g(x) =\frac{1}{n}[w ^ {-1}]\left(\frac{1}{f(w)}\right)^n \]

由于 \(\frac{1}{f(w)}\) 不能求逆,所以更常见的方式是

\[[x ^ n] g(x) =\frac{1}{n}[w ^ {n-1}]\left(\frac{w}{f(w)}\right)^n \]

\(\text{Lagrange}\) 反演 扩展形式

下面记 \(f(x)\) 的复合逆为 \(\hat{f}(x)\),有

\[[x ^ n] (h\circ g) (x) = \frac{1}{n} [x ^ {n - 1}]H'(x) \left (\frac{x}{f(x)}\right) ^ n \]

特殊情况,令 \(h(x) = x ^ k\)

那么有

\[[x ^ n] g^k(x) = \frac{k}{n}[u ^ {n - k}]\left(\frac{u}{f(u)}\right) ^ n \]

\(f(g(x)) = h(x)\)。应该有 \(g(x) = h(\hat{f}(x))\)

这时候居然有

\[[x ^ n]g(x) =\frac{1}{n} [x ^ {n - 1}]H'(x) \left (\frac{x}{f(x)}\right) ^ n \]

和之前的式子一样。

\(\text{Lagrange}\) 反演 另类形式

\(g(x)\)\(f(x)\) 的复合逆,则

\[[x ^ n]g ^ k(x) = [x ^{-k - 1}] \frac{F'(x)}{F ^ {n + 1}(x)} \]

改写一下因该有

\[[x ^ n] g ^ k(x) = [x ^ {n - k}] F'(x)\left (\frac{x}{F(x)}\right) ^ {n + 1} \]

这可以求解不同的 \(k\) 的问题。

ABC222H

题目链接 H - Beautiful Binary Tree (atcoder.jp)

中文翻译一下:给你一个正整数 \(n\),满足一下条件的为 \(N\) 阶美丽二叉树。

  • 每个顶点上写着 \(0/1\)
  • 每个叶子节点上写着 \(1\)
  • 可以进行以下操作 \(N - 1\) 次,使得根节点写上 \(N\),其他节点写上 \(0\)。操作为:选择两个节点 \(u,v\),其中 \(v\) 一定是 \(u\) 的儿子或者是孙子。将 \(a_u + a_v \to a_u,a_v \to 0\)

首先根节点一定是 \(1\),并且不应该有相邻的 \(0\)

怎么办?考虑一个朴素的 \(dp\) 吧。令 \(f_n\) 表示原问题的答案,然后 \(g_n\) 是强制令根为 \(0\),但是每个儿子都是美丽二叉树的方案。

那么应该有

\[g_n = 2 f_n + \sum_{0 < j < n} f_j f_{n - j} \]

然后可以得到

\[\begin{aligned} f_1 &= 1\\ f_n &= 2(f_{n - 1} + g_{n - 1}) + \sum_{0 < j < i - 1} (f_j + g_j) (f _ {i - 1 - j} + g_{i - 1 - j}) \end{aligned} \]

可以使用 分治NTT来计算,复杂度为 \(O(N \log^2 N)\)

但是题目要求 \(n\le 10^7\),时间绝对超时。考虑生成函数

\[g(x) = 2f(x) + f ^ 2(x) \]

然后

\[f(x) = x + 2xf(x) + 2x g(x) + x (f(x) + g(x)) ^ 2 \]

\[f(x) = x(1 + 3f(x) + f^2(x))^2 \]

考虑拉格朗日反演,\(f(x) = x(1 + 3f(x) +f ^ 2(x))\),他的复合逆就是 \(g(x) = \frac{x}{(1 + 3x + x ^ 2)^2}\)

所以说 \([x ^ N]F(x) = \frac{1}{N}[x ^ {N - 1}](1 + 3x + x^2)^{2N}\) 这玩意怎么算?

二项式定理展开答案就是 \(\frac{\sum_{j = 0} ^ {2N}\binom{2n}{j} \binom{2n - j}{n - 1 - 2j}3 ^ {n - 1 - 2j}}{n}\)

fuss-catalan 数

求有 \(N\) 个内点的 \(K\) 叉树数量,满足儿子内有顺序,满足非叶子节点都有 \(K\) 个儿子。

一个等价的问题,从 \((0,0)\) 走到 \((kn,0)\),然后每次可以走 \((+1,+1)\) 或者是 \((+1,-(k - 1))\),并且 \(y\) 坐标不能在 \(0\) 以下的路径方案数量。

用生成函数来表达答案 \(f(x) = x (f(x) + 1) ^ k\),求出来 \(f_n\) 即可。。

得到 \(x = \frac{f(x)}{(f(x) + 1)^ k}\)。所以 $f(x) $ 的复合逆是 \(\frac{x}{(x + 1) ^ k}\)。根据拉格朗日反演。

\[f_n = [x ^ n]f(x) = \frac{1}{n} [x ^ {n- 1}]\left(\frac{x}{g(x)}\right)=\frac{1}{n}[x ^ {n - 1}](x + 1)^{kn}=\frac{\binom{kn}{n - 1}}{n} \]

所以解决了。

posted @ 2022-05-21 17:02  siriehn_nx  阅读(214)  评论(1编辑  收藏  举报