1. 程式人生 > 其它 >多項式複合和拉格朗日反演 學習筆記

多項式複合和拉格朗日反演 學習筆記

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} \]

所以解決了。