ケロシのポリ - 多项式学习笔记

ケロシのポリ - 多项式学习笔记

持续更新中……

多项式乘法

给出 F(x)G(x),求出 H(x)=F(x)G(x)

考虑用 NTT 求出点值,而点值可以直接相乘。

然后对乘出来的点值做一遍逆变换即可。

时间复杂度 O(nlogn)

ケロシの代码
void solve() {
	cin >> n >> m;
	FOR(i, 0, n) cin >> a[i];
	FOR(i, 0, m) cin >> b[i];
	int lim = 1, l = 0;
	while(lim <= (n + m)) lim <<= 1, l++;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	NTT(a, lim, 1);
	NTT(b, lim, 1);
	REP(i, lim) Mul(a[i], b[i]);
	NTT(a, lim, -1);
	FOR(i, 0, n + m) cout << a[i] << " ";
	cout << endl;
}

多项式乘法逆

给出 F(x),求 G(x) 使得 F(x)G(x)1(modxn)

首先 G(x)0=1F(x)0,然后考虑倍增,用 modxn2 的答案 G0(x)modxn 的答案:

F(x)G0(x)1(modxn2)F(x)G(x)1(modxn2)G(x)G0(x)0(modxn2)(G(x)G0(x))20(modxn)G(x)22G(x)G0(x)+G02(x)0(modxn)G(x)2G0(x)+F(x)G02(x)0(modxn)G(x)2G0(x)F(x)G02(x)(modxn)

需要用到 O(nlogn) 的多项式乘法,计算一下复杂度:

T(n)=T(n2)+O(nlogn)

所以复杂度 T(n)=O(nlogn)

ケロシの代码
int c[N];
void invs(int * a, int * b, int d) {
	if(d == 1) {
		b[0] = fp(a[0], P - 2);
		return;
	}
	invs(a, b, (d + 1) >> 1);
	int lim = 1, l = 0;
	while(lim < (d << 1)) lim <<= 1, l ++;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	REP(i, d) c[i] = a[i];
	FOR(i, d, lim - 1) c[i] = 0;
	NTT(b, lim, 1);
	NTT(c, lim, 1);
	REP(i, lim) Mul(b[i], sub(2, mul(b[i], c[i])));
	NTT(b, lim, -1);
	FOR(i, d, lim - 1) b[i] = 0;
}

多项式开根

给出 F(x),求 G(x) 使得 G2(x)F(x)(modxn)

先考虑常数项,那么 G0(x) 就是 F0(x) 的一个二次剩余。

然后还是考虑倍增,用 modxn2 的答案 G0(x)modxn 的答案:

G02(x)F(x)(modxn2)G02(x)F(x)0(modxn2)(G02(x)F(x))20(modxn)(G02(x)+F(x))24G02(x)F(x)(modxn)(G02(x)+F(x)2G0(x))2F(x)(modxn)G02(x)+F(x)2G0(x)G(x)(modxn)G0(x)2+F(x)2G0(x)G(x)(modxn)

时间复杂度 O(nlogn)

多项式在 lnexp 上的意义

一般来说,lnexp 都是对于实数的,那么要计算多项式等形式的 lnexp 需要用到泰勒公式进行展开:

exp(x)=x00!+x11!+x22!+x33!+

ln(x+1)=x11x22+x33x44+

我们把式子中的 x 替换成多项式即可使运算有意义。

为了使其不无限循环下去,式子中的多项式的 x0 常数项必须为 0,使得 xn 及以后 modxn 都等于 0

所以当多项式 F(x) 满足 F(x)0=1ln(F(x)) 有意义,
当多项式 F(x) 满足 F(x)0=0exp(F(x)) 有意义。

多项式对数函数 ln

给出 F(x),求 G(x) 使得 G(x)ln(F(x))(modxn)

对于 ln(x),注意到其求导后即为 1x,比较好算,所以考虑两边求导。

对于复合函数 f(g(x)),其求导需要遵循链式法则:f(g(x))=f(g(x))g(x)

G(x)ln(F(x))(modxn)G(x)ln(F(x))(modxn)G(x)ln(F(x))F(x)(modxn)G(x)F(x)F(x)(modxn)G(x)F(x)F(x)dx(modxn)

需要多项式求逆和多项式乘法,时间复杂度 O(nlogn)

ケロシの代码
int p[N], q[N];
void lns(int * a, int * b, int d) {
	int lim = 1, l = 0;
	while(lim < (d << 1)) lim <<= 1, l++;
	REP(i, lim) q[i] = 0;
	invs(a, q, d);
	FOR(i, 1, d - 1) p[i - 1] = mul(i, a[i]);
	FOR(i, d - 1, lim - 1) p[i] = 0;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	NTT(p, lim, 1);
	NTT(q, lim, 1);
	REP(i, lim) Mul(p[i], q[i]);
	NTT(p, lim, - 1);
	FOR(i, 1, d) b[i] = mul(p[i - 1], inv[i]);
	b[0] = 0;
}

多项式牛顿迭代

给出多项式 F(x),求 F(x) 的零点 G(x),即满足 F(G(x))0(modxn)

考虑使用牛顿迭代来进行倍增。

先单独考虑常数项 F(G(x)0)0=0

接下来使用牛顿迭代,用 modxn2 的答案 G0(x)modxn 的答案。

考虑泰勒展开公式:

f(x)=f(x0)(xx0)00!+f(x0)(xx0)11!+f(x0)(xx0)22!+

考虑将泰勒展开公式中的 xx0 分别代入 G(x)G0(x),那么 (G(x)G0(x))2 及以后的项 modxn 都等于 0,所以直接代入泰勒展开公式:

F(G(x))F(G0(x))+F(G0(x))(G(x)G0(x))(modxn)

因为 F(G(x))0(modxn)

所以可以推出 G(x)

F(G(x))F(G0(x))+F(G0(x))(G(x)G0(x))(modxn)0F(G0(x))+F(G0(x))(G(x)G0(x))(modxn)F(G0(x))F(G0(x))(G(x)G0(x))(modxn)F(G0(x))F(G0(x))G(x)G0(x)(modxn)G(x)G0(x)F(G0(x))F(G0(x))(modxn)

多项式指数函数 exp

给出 F(x),求 G(x) 使得 G(x)exp(F(x))(modxn)

考虑将求 exp 转换成关于 ln 的零点,

G(x)exp(F(x))(modxn)ln(G(x))F(x)(modxn)ln(G(x))F(x)0(modxn)

那么 G(x) 就是函数 H(x)=ln(x)+F 的零点。

接下来使用牛顿迭代,代入公式。

但是需要求得 H(x),因为自变量 x 是多项式,F 也是多项式,所以对于 x 来说 F 就是常数项,所以 H(x)=ln(x)=1x

然后直接代入公式即可:

G(x)G0(x)H(G0(x))H(G0(x))(modxn)G(x)G0(x)(ln(G0(x))F(x))G0(x)(modxn)G(x)G0(x)(1ln(G0(x))+F(x))(modxn)

需要用到多项式乘法,多项式乘法逆和多项式对数函数 ln

时间复杂度 O(nlogn)

ケロシの代码
int h[N];
void exps(int * a, int * b, int d) {
	if(d == 1) {
		b[0] = 1;
		return;
	}
	exps(a, b, (d + 1) >> 1);
	REP(i, d) h[i] = 0;
	lns(b, h, d);
	REP(i, d) h[i] = sub(a[i], h[i]);
	Add(h[0], 1);
	int lim = 1, l = 0;
	while(lim < (d << 1)) lim <<= 1, l++;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	FOR(i, d, lim - 1) h[i] = 0;
	NTT(b, lim, 1);
	NTT(h, lim, 1);
	REP(i, lim) Mul(b[i], h[i]);
	NTT(b, lim, - 1);
	FOR(i, d, lim - 1) b[i] = 0;
}

多项式快速幂

给出 F(x)k,求 G(x) 使得 G(x)F(x)k(modxn),保证 F(x)0=1

考虑两边同时取对数下放指数:

G(x)F(x)k(modxn)ln(G(x))ln(F(x)k)(modxn)ln(G(x))kln(F(x))(modxn)G(x)exp(kln(F(x)))(modxn)

时间复杂度 O(nlogn)

多项式连续点值平移

给定不超过 n 次多项式的 n+1 个点值:f(0),f(1),f(2),,f(n+1),并且给定 m,满足 m>n,求出 f(m),f(m+1),f(m+2),,f(m+n)

考虑拉格朗日插值求出 x>n 的点值:

f(x)=i=0nf(i)jixjij=i=0nf(i)x!(xi)(xn1)!i!(ni)!(1)ni

考虑卷积优化,把式子拆成关于 ixix 的部分:

f(x)=i=0nf(i)x!(xi)(xn1)!i!(ni)!(1)ni=x!(xn1)!i=0nf(i)i!(ni)!(1)ni1xi

为了方便卷积,对齐下标:

f(m+x)=(m+x)!(m+xn1)!i=0nf(i)i!(ni)!(1)ni1m+xi

拆成两个函数,注意下标必须要正的,所以将 xi 改成 x+ni 即可:

F(i)=f(i)i!(ni)!(1)ni

G(x+ni)=1m+xi

G(i)=1mn+i

然后卷起来即可:

f(m+x)=i=0n(m+xi)i=0nF(i)G(x+ni)

时间复杂度 O(nlogn)

ケロシの代码
int n, m, a[N], F[N], G[N];
int fac[N], finv[N];
void solve() {
	cin >> n >> m;
	FOR(i, 0, n) cin >> a[i];
	fac[0] = 1;
	FOR(i, 1, n) fac[i] = mul(fac[i - 1], i);
	finv[n] = fp(fac[n], P - 2);
	ROF(i, n - 1, 0) finv[i] = mul(finv[i + 1], i + 1);
	FOR(i, 0, n) F[i] = mul({a[i], finv[i], finv[n - i], sgn(n - i)});
	FOR(i, 0, n << 1) G[i] = fp(m - n + i, P - 2);
	int lim = 1, l = 0;
	while(lim <= (n << 1)) lim <<= 1, l ++;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	NTT(F, lim, 1);
	NTT(G, lim, 1);
	REP(i, lim) Mul(F[i], G[i]);
	NTT(F, lim, - 1);
	int res = 1;
	FOR(i, 0, n) Mul(res, m - i);
	FOR(i, 0, n) {
		cout << mul(F[n + i], res) << " ";
		Mul(res, fp(m + i - n, P - 2));
		Mul(res, m + i + 1);
	}
	cout << endl;
}

Chirp Z-Transform

给定一个 n 次多项式 F(x),给定 cm,求出 F(c0),F(c1),F(c2),,F(cm1)

考虑:

F(ci)=j=0n1Fjcij

我们希望用卷积加速这个式子,但是有有关 ij 的项,不好处理。

注意到 ij 是在指数上,拆成加减后会变成乘法,比较好做,所以考虑拆解 ij

2ij=(i+j)2i2j2ij=(i+j)22i22j22

但是这个式子的指数有除以二,需要二次剩余,不好做。

考虑另外一种拆法:

2ij=(i+j)2i2j2=(i+j)2i2j2(i+j)+i+j=(i+j)(i+j1)i(i1)j(j1)ij=(i+j)(i+j1)2i(i1)2j(j1)2=(i+j2)(i2)(j2)

这样就都是整数了,代入式子:

F(ci)=j=0n1Fjcij=j=0n1Fjc(i+j2)(i2)(j2)=c(i2)j=0n1Fjc(j2)c(i+j2)

现在就是一部分只和 j 有关,另一部分之和 i+j 有关,变成卷积形式了,直接卷积即可。

假设 nm 同阶,时间复杂度 O(nlogn)

ケロシの代码
int n, c, m;
int a[N], pw1[B + 1], pw2[B + 1];
int F[N], G[N];
inline int pw(int x) {
	return mul(pw1[x % B], pw2[x / B]);
}
inline int C2(int x) {
	return (1ll * x * (x - 1) / 2) % (P - 1);
}
void solve() {
	cin >> n >> c >> m;
	REP(i, n) cin >> a[i];
	pw1[0] = pw2[0] = 1; 
	FOR(i, 1, B) pw1[i] = mul(pw1[i - 1], c);
	FOR(i, 1, B) pw2[i] = mul(pw2[i - 1], pw1[B]);
	REP(i, n + m) F[i] = pw(C2(i));
	REP(i, n) G[i] = mul(a[i], pw(P - 1 - C2(i)));
	reverse(G, G + n);
	int lim = 1, l = 0;
	while(lim <= (n + m)) lim <<= 1, l ++;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	NTT(F, lim, 1);
	NTT(G, lim, 1);
	REP(i, lim) Mul(F[i], G[i]);
	NTT(F, lim, - 1);
	REP(i, m) cout << mul(F[i + n - 1], pw(P - 1 - C2(i))) << " "; cout << endl;
}

任意模数多项式乘法

能做 NTT 的模数需要大量单位根,所以不是所有的数都能做 NTT 的模数。

考虑使用三个原根都是 3 的模数:998244353,469762049,1004535809

对于每个模数都做多项式乘法,然后使用 CRT 把答案解出来即可。

时间复杂度 O(nlogn)

ケロシの代码
const int N = 1e6 + 5;
const int P1 = 998244353;
const int P2 = 469762049;
const int P3 = 1004535809;
const int I1 = 208783132;
const int I2 = 395249030;
inline int add1(int x, int y) { return (x + y < P1 ? x + y : x + y - P1); }
inline void Add1(int & x, int y) { x = (x + y < P1 ? x + y : x + y - P1); }
inline int sub1(int x, int y) { return (x < y ? x - y + P1 : x - y); }
inline void Sub1(int & x, int y) { x = (x < y ? x - y + P1 : x - y); }
inline int mul1(int x, int y) { return (1ll * x * y) % P1; }
inline void Mul1(int & x, int y) { x = (1ll * x * y) % P1; }
inline int add2(int x, int y) { return (x + y < P2 ? x + y : x + y - P2); }
inline void Add2(int & x, int y) { x = (x + y < P2 ? x + y : x + y - P2); }
inline int sub2(int x, int y) { return (x < y ? x - y + P2 : x - y); }
inline void Sub2(int & x, int y) { x = (x < y ? x - y + P2 : x - y); }
inline int mul2(int x, int y) { return (1ll * x * y) % P2; }
inline void Mul2(int & x, int y) { x = (1ll * x * y) % P2; }
inline int add3(int x, int y) { return (x + y < P3 ? x + y : x + y - P3); }
inline void Add3(int & x, int y) { x = (x + y < P3 ? x + y : x + y - P3); }
inline int sub3(int x, int y) { return (x < y ? x - y + P3 : x - y); }
inline void Sub3(int & x, int y) { x = (x < y ? x - y + P3 : x - y); }
inline int mul3(int x, int y) { return (1ll * x * y) % P3; }
inline void Mul3(int & x, int y) { x = (1ll * x * y) % P3; }
int fp1(int x, int y) {
	int res = 1;
	for(; y; y >>= 1) {
		if(y & 1) Mul1(res, x);
		Mul1(x, x);
	}
	return res;
}
int fp2(int x, int y) {
	int res = 1;
	for(; y; y >>= 1) {
		if(y & 1) Mul2(res, x);
		Mul2(x, x);
	}
	return res;
}
int fp3(int x, int y) {
	int res = 1;
	for(; y; y >>= 1) {
		if(y & 1) Mul3(res, x);
		Mul3(x, x);
	}
	return res;
}
struct cint {
	int x1, x2, x3;
	cint operator + (const cint & A) {
		return {add1(x1, A.x1), add2(x2, A.x2), add3(x3, A.x3)};
	}
	cint operator - (const cint & A) {
		return {sub1(x1, A.x1), sub2(x2, A.x2), sub3(x3, A.x3)};
	}
	cint operator * (const cint & A) {
		return {mul1(x1, A.x1), mul2(x2, A.x2), mul3(x3, A.x3)};
	}
	cint & operator += (const cint & A) {
		Add1(x1, A.x1); Add2(x2, A.x2); Add3(x3, A.x3);
		return * this;
	}
	cint & operator -= (const cint & A) {
		Sub1(x1, A.x1); Sub2(x2, A.x2); Sub3(x3, A.x3);
		return * this;
	}
	cint & operator *= (const cint & A) {
		Mul1(x1, A.x1); Mul2(x2, A.x2); Mul3(x3, A.x3);
		return * this;
	}
	int crt(int P) {
		ll x4 = 1ll * P1 * mul2(sub2(x2, x1 % P2), I1) + x1;
		int k4 = mul3(sub3(x3, x4 % P3), I2);
		return (x4 + 1ll * k4 * (1ll * P1 * P2 % P)) % P;
	}
};
int rev[N];
void NTT(cint * a, int lim, int o) {
	REP(i, lim) if(i < rev[i]) swap(a[i], a[rev[i]]);
	for(int i = 1; i < lim; i <<= 1) {
		cint wn = {
			fp1(o == 1 ? 3 : (P1 + 1) / 3, (P1 - 1) / (i << 1)),
			fp2(o == 1 ? 3 : (P2 + 1) / 3, (P2 - 1) / (i << 1)),
			fp3(o == 1 ? 3 : (P3 + 1) / 3, (P3 - 1) / (i << 1))
		};
		for(int j = 0; j < lim; j += (i << 1)) {
			cint w = {1, 1, 1};
			for(int k = 0; k < i; k ++, w *= wn) {
				cint x = a[j + k];
				cint y = a[j + k + i] * w;
				a[j + k] = x + y;
				a[j + k + i] = x - y;
			}
		}
	}
	if(o == 1) return;
	cint inv = {fp1(lim, P1 - 2), fp2(lim, P2 - 2), fp3(lim, P3 - 2)};
	REP(i, lim) a[i] *= inv;
}
int n, m, P; cint F[N], G[N];
void solve() {
	cin >> n >> m >> P;
	FOR(i, 0, n) {
		int x; cin >> x; x %= P;
		F[i] = {x, x, x};
	}
	FOR(i, 0, m) {
		int x; cin >> x; x %= P;
		G[i] = {x, x, x};
	}
	int lim = 1, l = 0;
	while(lim <= (n + m)) lim <<= 1, l ++;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	NTT(F, lim, 1);
	NTT(G, lim, 1);
	REP(i, lim) F[i] *= G[i];
	NTT(F, lim, - 1);
	FOR(i, 0, n + m) cout << F[i].crt(P) << " "; cout << endl;
}

快速阶乘

给定 nP,求 n!modP

考虑分块,设快长 B=n,并考虑求解一段数连乘的值,考虑设多项式 Fd(x)=i=1d(x+i)

那么 n!=i=0B1FB(iB)i=B2+1ni,也就是求出 FB(x) 即可。

考虑求解 FB(x),但是在 FB(x) 需要的下标都是 iB,这启发直接计算点值。

考虑倍增求解点值,考虑 Fd(iB) 如何转移到 F2d(iB),发现 F2d(x)=Fd(x)Fd(x+d)

所以直接将 Fd(iB) 进行连续点值平移到 Fd(iB+d) 也就是 Fd((i+dB)B) 即可。

但这样是需要两只 log

考虑缩减范围,Fd(x) 就是一个 d+1 次多项式,只保留 Fd(0),Fd(B),Fd(2B),,Fd(dB) 一共 d+1 即可。

倍增到 F2d(x) 的时候再做一次点值平移得出 Fd((d+1)B),Fd((d+1)B),,Fd(2dB) 即可。

这样时间复杂度就是 O(nlogn)

ケロシの代码
int n, P, B;
int a[N], b[N];
cint F[N], G[N];
int fac[N], finv[N];
void moves(int * a, int * b, int n, int m) {
	int lim = 1, l = 0;
	while(lim <= (n << 1)) lim <<= 1, l ++;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	REP(i, lim) F[i] = G[i] = Cint(0);
	FOR(i, 0, n) F[i] = Cint(mul({a[i], finv[i], finv[n - i], sgn(n - i)}));
	FOR(i, 0, n << 1) G[i] = Cint(inv(sub(m, n - i)));
	NTT(F, lim, 1);
	NTT(G, lim, 1);
	REP(i, lim) F[i] *= G[i];
	NTT(F, lim, - 1);
	int res = 1;
	FOR(i, 0, n) Mul(res, sub(m, i));
	FOR(i, 0, n) {
		b[i] = mul(F[n + i].crt(P), res);
		Mul(res, inv(sub(m, n - i)));
		Mul(res, add(m, i + 1));
	}
}
void facs(int d) {
	if(d == 1) {
		a[0] = 1; a[1] = B + 1;
		return;
	}
	facs(d >> 1);
	moves(a, b, d >> 1, (d >> 1) + 1);
	REP(i, d) a[(d >> 1) + i + 1] = b[i];
	moves(a, b, (d >> 1) << 1, mul(d >> 1, inv(B)));
	FOR(i, 0, (d >> 1) << 1) Mul(a[i], b[i]);
	if(d & 1) {
		REP(i, d) Mul(a[i], i * B + d);
		a[d] = 1;
		FOR(i, 1, d) Mul(a[d], d * B + i);
	}
}
void solve() {
	cin >> n >> P;
	B = sqrt(n);
	fac[0] = 1;
	FOR(i, 1, B) fac[i] = mul(fac[i - 1], i);
	finv[B] = fp(fac[B], P - 2);
	ROF(i, B - 1, 0) finv[i] = mul(finv[i + 1], i + 1);
	facs(B);
	int ans = 1;
	REP(i, B) Mul(ans, a[i]);
	FOR(i, B * B + 1, n) Mul(ans, i);
	cout << ans << endl;
}

第二类斯特林数行

给出 n,对于 i[0,n],求出 {ni}

考虑第二类斯特林数的通项公式:

{ni}=j=0i(1)ijjnj!(ij)!=j=0ijnj!(1)ij(ij)!

直接设多项式 Fi=ini!Gi=(1)ii!,直接卷积求解即可。

时间复杂度 O(nlogn)

ケロシの代码
int n;
int fac[N], finv[N];
int F[N], G[N];
void init(int n) {
	fac[0] = 1;
	FOR(i, 1, n) fac[i] = mul(fac[i - 1], i);
	finv[n] = fp(fac[n], P - 2);
	ROF(i, n - 1, 0) finv[i] = mul(finv[i + 1], i + 1);
}
void solve() {
	cin >> n;
	init(n);
	int lim = 1, l = 0;
	while(lim <= (n << 1)) lim <<= 1, l ++;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	FOR(i, 0, n) F[i] = mul(fp(i, n), finv[i]);
	FOR(i, 0, n) G[i] = mul(sgn(i), finv[i]);
	NTT(F, lim, 1);
	NTT(G, lim, 1);
	REP(i, lim) Mul(F[i], G[i]);
	NTT(F, lim, - 1);
	FOR(i, 0, n) cout << F[i] << " "; cout << endl;
}

第二类斯特林数列

给出 nk,对于 i[0,n],求出 {ik}

考虑组合意义,先乘上 k! 变成 i 个有标号球放进 k 个有标号非空盒子里,考虑每次加一个盒子,相当于选择放多少个球再分配标号,不难发现 EGF 的乘法可以做这个。

所以设只放一个盒子的 EGF,除了放 0 个都有 1 的方案,所以设 EGF F=i=1xii!=ex1

所以答案的 EGF 即为 Fkk!

使用多项式快速幂即可。

时间复杂度 O(nlogn)

ケロシの代码
int n, k;
int F[N], G[N];
void solve() {
	cin >> n >> k;
	if(n < k) {
		REP(_, n + 1) cout << 0 << " ";
		return;
	}
	init(n);
	int len = n - k + 1;
	REP(i, len) F[i] = finv[i + 1];
	lns(F, G, len);
	REP(i, len) Mul(G[i], k);
	exps(G, F, len);
	REP(_, k) cout << 0 << " ";
	REP(i, len) cout << mul({F[i], fac[i + k], finv[k]}) << " "; cout << endl;
}

第一类斯特林数行

给出 n,对于 i[0,n],求出 [ni]

xn¯=i=0n[ni]xi

所以只要求 xn¯ 即可,考虑倍增:

x2n¯=xn¯(x+n)n¯

然后考虑 (x+n)n¯ 怎么求:

(x+n)n¯=i=0n[ni](x+n)i=i=0n[ni]j=0i(ij)xjnij=i=0n[ni]j=0ii!j!(ij)!xjnij=j=0nxjj!i=jn[ni]i!(ij)!nij=j=0nxjj!i=jni![ni]nij(ij)!

右边很明显是一个卷积形式,设 Fi=i![ni]Gni=nii!,直接卷即可。

时间复杂度 O(nlogn)

ケロシの代码
int n, a[N], b[N];
int fac[N], finv[N];
int F[N], G[N];
void init(int n) {
	fac[0] = 1;
	FOR(i, 1, n) fac[i] = mul(fac[i - 1], i);
	finv[n] = fp(fac[n], P - 2);
	ROF(i, n - 1, 0) finv[i] = mul(finv[i + 1], i + 1);
}
void slv(int d) {
	if(d == 1) {
		a[1] = 1;
		return;
	}
	if(d & 1) {
		slv(d - 1);
		FOR(i, 0, d) F[i] = 0;
		REP(i, d) Add(F[i + 1], a[i]);
		REP(i, d) Add(F[i], mul(a[i], d - 1));
		FOR(i, 0, d) a[i] = F[i];
		return;
	}
	slv(d >> 1);
	int len = d >> 1;
	int lim = 1, l = 0;
	while(lim <= d) lim <<= 1, l ++;
	REP(i, lim) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	REP(i, lim) F[i] = G[i] = 0;
	FOR(i, 0, len) F[i] = mul(fac[i], a[i]);
	FOR(i, 0, len) G[len - i] = mul(finv[i], fp(len, i));
	NTT(F, lim, 1);
	NTT(G, lim, 1);
	REP(i, lim) Mul(F[i], G[i]);
	NTT(F, lim, - 1);
	REP(i, lim) b[i] = 0;
	FOR(i, 0, len) b[i] = mul(finv[i], F[len + i]);
	NTT(a, lim, 1);
	NTT(b, lim, 1);
	REP(i, lim) Mul(a[i], b[i]);
	NTT(a, lim, - 1);
}
void solve() {
	cin >> n;
	init(n);
	slv(n);
	FOR(i, 0, n) cout << a[i] << " "; cout << endl;
}

第一类斯特林数列

给出 nk,对于 i[0,n],求出 [ik]

考虑使用与第二类斯特林数列相同的做法,但是注意单个环的方案是 (i1)!,所以 EGF F=i=1(i1)!xii!=i=1xii=ln(11x)

时间复杂度 O(nlogn)

ケロシの代码
int n, k;
int F[N], G[N];
void solve() {
	cin >> n >> k;
	if(n < k) {
		REP(_, n + 1) cout << 0 << " ";
		return;
	}
	init(n);
	int len = n - k + 1;
	REP(i, len) F[i] = inv[i + 1];
	lns(F, G, len);
	REP(i, len) Mul(G[i], k);
	exps(G, F, len);
	REP(_, k) cout << 0 << " ";
	REP(i, len) cout << mul({F[i], fac[i + k], finv[k]}) << " "; cout << endl;
}
posted @   KevinLikesCoding  阅读(142)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示