多项式相关

慢慢更,想学的时候就学一点。给自己看的,随便写写。

拉格朗日插值法

给定 \(n\) 个点值 \(f(x_i)=y_i\),我们断言,能够唯一确定一个 \(n-1\) 项的多项式 \(f\)

基本形式

对于任意 \(x_0\)

\[f(x_0)=\sum\limits_{i=1}^ny_i\prod\limits_{i\ne j}\dfrac{x_0-x_j}{x_i-x_j} \]

这是很好证明的:显然 \(f\)\(n-1\) 项的多项式;注意到 \(\mathscr F_i(x_0)=\prod\limits_{i\ne j}\dfrac{x_0-x_j}{x_i-x_j}\) 满足 \(\mathscr F_i(x_i)=1\)\(\mathscr F_i(x_j)=0\)\(i\ne j\)),所以构造出的函数 \(f(x_0)=\sum y_i\mathscr F_i(x_0)\) 必定符合 \(n\) 个点值。显然多项式是唯一的,所以 \(f\) 当然也是确定的啦。

显然是 \(O(n^2)\) 的,至少比高斯消元快 /bx

\(\text{ }\)

点值连续的快速做法

......好吧挺唐氏的,就是在 \(x_i\) 有一定规律时,快速预处理出上文的 \(\mathscr F_i\) 即可,做到线性。

一个经典应用:CF622F。题意:求 \(\sum_{i=1}^n i^k\)。注意到这是一个关于 \(n\)\(k+1\) 次多项式,所以只要随便求出 \(k+2\) 个点值(即 \(n\) 对应的 \(\sum_{i=1}^n i^k\)),然后插值一下就搞定。我们显然是取 \(0\le n\le k+1\) 的位置,不难得到 \(O(k\log k)\) 解法。

\(\text{ }\)

\(\text{ }\)

FMT&FWT

给定序列 \(f,g\),求解 \(h\) 满足

\[h_i=\sum\limits_{j\otimes k=i}A_jB_k \]

其中 \(\otimes\) 符号可以是 OR,AND(FMT)或 XOR(FWT)。

核心思想:构造序列变换 \(a\to T(a)\),满足 \(T(a)_i\times T(b)_i=T(c)_i\),这样只需要对 \(f,g\) 进行变换后直接求出 \(T(h)\),然后快速求出 \(T(h)\),再进行逆变换 \(T(h)\to h\) 得到序列 \(h\)

变换方式统一为:类似高维前缀和,枚举每一位 \(b\),看 \(i,i\oplus 2^b\) 互相造成的贡献,然后递推。

三个操作复杂度均可以做到 \(O(n2^n)\)。构造方法如下(证明略,可以看 OI-Wiki)

OR

\(T(a)_i=\sum\limits_{j|i=i}a_j\)

正变换 \(op=1\),逆变换 \(op=-1\)

void OR(int f[], int op) {
	for (int k = 1; k < (1 << n); k <<= 1) for (int i = 0; i < (1 << n); i += (k << 1)) for (int j = 0; j < k; j++)
		f[i + j + k] = ((ll)f[i + j + k] + op * f[i + j] + mod) % mod;
}

AND

\(T(a)_i=\sum\limits_{j\&i=i}a_j\)

正变换 \(op=1\),逆变换 \(op=-1\)

void AND(int f[], int op) {
	for (int k = 1; k < (1 << n); k <<= 1) for (int i = 0; i < (1 << n); i += (k << 1)) for (int j = 0; j < k; j++)
		f[i + j] = ((ll)f[i + j] + op * f[i + j + k] + mod) % mod;
}

XOR

\(T(a)_i=\left(\sum_{\operatorname{popc}(i\& j)\equiv0\pmod2} a_j\right)-\left(\sum_{\operatorname{popc}(i\& j)\equiv1\pmod2} a_j\right)\)

正变换 \(op=1\),逆变换 \(op=\frac12\)

void XOR(int f[], int op) {
	for (int k = 1; k < (1 << n); k <<= 1) for (int i = 0; i < (1 << n); i += (k << 1)) for (int j = 0; j < k; j++) {
		int x = f[i + j], y = f[i + j + k];
		f[i + j] = 1ll * op * (x + y) % mod, f[i + j + k] = 1ll * op * (x - y + mod) % mod;
	}
}

\(\text{ }\)

\(\text{ }\)

子集卷积

本质只是一道 FMT 的练习题而已。。

给定序列 \(f,g\),求解 \(h\) 满足

\[h_i=\sum\limits_{\substack{j|k=i\\j\&k=0}}A_jB_k \]

核心思想\(j|k=i,j\&k=0\) 的一个等价描述是 \(j|k=i,\operatorname{popc}(j)+\operatorname{popc}(k)=\operatorname{pop}(j|k)\)

popcount 是 \(O(n)\) 的,可以直接枚举 popcount 的值消除第二个限制,然后第一个限制 OR-FMT 解决。

复杂度 \(O(n^22^n)\)

for (int i = 0; i < (1 << n); i++) scanf("%d", &a[popc[i]][i]);
for (int i = 0; i < (1 << n); i++) scanf("%d", &b[popc[i]][i]);
for (int i = 0; i <= n; i++) fmt(a[i], 1), fmt(b[i], 1);
for (int i = 0; i <= n; i++) for (int S = 0; S < (1 << n); S++) for (int j = 0; j <= i; j++)
	ans[i][S] = (ans[i][S] + 1ll * a[j][S] * b[i - j][S]) % mod;
for (int i = 0; i <= n; i++) fmt(ans[i], -1);
for (int i = 0; i < (1 << n); i++) printf("%d ", ans[popc[i]][i]);

\(\text{ }\)

\(\text{ }\)

FFT

给定两个多项式 \(F,G\),求它们的乘积,具体即求 \(H_i=\sum\limits_{j+k=i}F_jG_k\)

核心思想:有点类似 fwt,考虑找到一个媒介做正逆变换:对 \(F\) 取出 \(n\) 个位置的点值从而唯一确定多项式,\(G\) 同理,点值可以直接乘得到 \(H\),然后对 \(H\) 做点值到多项式的逆变换得到 \(H\) 多项式。

核心思想2:取出 \(1,\omega_n,\omega_n^2,\cdots,\omega_n^{n-1}\) 作为点值。

多项式->点值

现在考虑对多项式 \(F\) 转为点值。设其系数为 \(a_{0\sim n-1}\),我们有

\[F(x)=a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1} \]

不妨认为 \(n\) 是偶数,考虑按下标奇偶性分类:

\[F(x)=(a_0+a_2x^2+\cdots+a_{n-2}x^{n-2})+x(a_1+a_3x^2+\cdots+a_{n-1}x^{n-2}) \]

从中取出两个函数,我们有

\[\begin{aligned}A(x)&=a_0+a_2x+a_4x^2+\cdots+a_{n-2}x^{n/2-1}\\B(x)&=a_1+a_3x+a_5x^2+\cdots+a_{n-1}x^{n/2-1}\end{aligned} \]

\[\implies F(x)=A(x^2)+xB(x^2) \]

现在我们要引入 \(\omega_n\) 了。这个是定义域在复数上的 \(n\) 次单位根,即 \(\omega_n^n=1\)。它的性质很多,首先将它作为复数放在二维坐标系上,相当于把圆等分为 \(2n\) 段取,所以 \(\omega_n^k=\cos k\cdot\frac{2\pi}n+\sin k\cdot\frac{2\pi}ni\)。其次,很难不注意到 \(\omega_n^{k+n}=\omega_n^k\);在 \(n\) 为偶数时,\(\omega_n^k=\omega_{n/2}^{k/2}\)\(\omega_n^{k+n/2}=-\omega_n^k\)。这些在数学中其实都是很基础的,但是我们已经可以完成 FFT 的步骤了。

我们代入 \(F(x=\omega_n^k)\),得到

\[\begin{aligned}F(\omega_n^k)&=A(\omega_n^{2k})+\omega_n^kB(\omega_n^{2k})\\&=A(\omega_{n/2}^k)+\omega_n^kB(\omega_{n/2}^k)\end{aligned} \]

我们代入 \(F(x=\omega_n^{k+n/2})\),得到

\[\begin{aligned}F(\omega_n^{k+n/2})&=A(\omega_n^{2k+n})+\omega_n^{k+n/2}B(\omega_n^{2k+n})\\&=A(\omega_{n/2}^k)-\omega_n^kB(\omega_{n/2}^k)\end{aligned} \]

我去,有没有很神奇!!\(A,B\) 的对应位置竟然不变,也就是我们只要求出 \(A(\omega_{n/2}^k)\)\(B(\omega_{n/2}^k)\) 的值,就能算出 \(F(\omega_n^k)\)\(F(\omega_n^{k+n/2})\) 的值。这是一个子问题结构,直接递归进去,每次 \(n\) 会除以 \(2\),yy 一下复杂度就是对的!\(O(n\log n)\)

\(\text{ }\)

点值->多项式

草这个我真懒得看了,直接记吧,就是搞 \(op=-1\) 直接做 ntt 逆变换,然后最后给 \(a_i\) 都除以 \(n\) 就是多项式了。

\(\text{ }\)

实现技巧

递归比较慢,无法通过洛谷模板题。于是一个实现技巧是去除递归,就是注意到递归每次会把奇偶位置的值分别存到两个位置去,打表发现二进制位会有一个反转的操作,就像这样

(图片从洛谷题解盗的)

反正就是随便怎么搞搞?嘟嘟嘟嘟嘟嘟嘟嘟,我现在怎么这么魔怔了

void fft(int n, CPX A[], int op) {
	for (int i = 0; i < n; i++) if (i < r[i]) swap(A[i], A[r[i]]);
	for (int k = 1; k < n; k <<= 1) {
		CPX wn = {cos(M_PI / k), op * sin(M_PI / k)};
		for (int i = 0; i < n; i += (k << 1)) {
			CPX w(1, 0); for (int j = 0; j < k; j++, w *= wn) {CPX x = A[i + j], y = w * A[i + j + k]; A[i + j] = x + y, A[i + j + k] = x - y;}
		}
	}
	if (op == -1) {for (int i = 0; i < n; i++) A[i] /= n;}
}

其中,正变换 \(op=1\),逆变换 \(op=-1\)

有没有发现 fft 的代码有点类似 fwt 的!!太神奇了多项式。

哦对有个细节,由于有大量 \(n\to \frac n2\) 的操作,最好在开始时把多项式长度补全为 \(2\) 的幂次。

哦对再给一个求 \(r\) 的代码,\(r_i\)\(i\) 的二进制反转:

//O(nlogn) 直接做
for (int i = 0; i < lim; i++) {r[i] = 0; for (int j = 0; j < w; j++) if (i >> j & 1) r[i] ^= (1 << (w - j - 1));}

//O(n), 应该也很好理解,我不写原因啦
for (int i = 0; i < lim; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (w - 1));

\(\text{ }\)

\(\text{ }\)

NTT

可以把 FFT 的模板认为模数为 \(998244353\) 然后做 NTT。

核心思想:算 \(\omega_n\) 要进复数太唐了,考虑在模意义下一个类似的东西叫原根。直接把单位根替换为原根就行,再稍微改改,就能在整数域下完成操作啦,无论是常数还是 intdouble 的精度差距,NTT 都吊打 FFT。

void ntt(int n, int A[], int op) {
	for (int i = 0; i < n; i++) if (i < r[i]) swap(A[i], A[r[i]]);
	for (int k = 1; k < n; k <<= 1) {
		int wn = qpow(op, (mod - 1) / (k << 1));
		for (int i = 0; i < n; i += (k << 1)) for (int j = 0, w = 1; j < k; j++, w = 1ll * w * wn % mod) {
			int x = A[i + j], y = 1ll * w * A[i + j + k] % mod; A[i + j] = (x + y) % mod, A[i + j + k] = (x - y + mod) % mod;
		}
	}
	if (op != G) {int inv = qpow(n, mod - 2); for (int i = 0; i < n; i++) A[i] = 1ll * A[i] * inv % mod;}
}

其中,正变换 \(op=g\),逆变换 \(op=g^{-1}\)\(g\) 为原根。

注意模数 \(p=k2^n+1\) 时,需保证多项式长度 \(\le2^n\)

  • \(p=998244353\)\(g=3\)
  • \(p=1004535809\)\(g=3\)
  • 猫がかわいい

\(\text{ }\)

\(\text{ }\)

分治 FFT / NTT

就是半在线卷积,给定 \(g\),求 \(f_i=\sum_{j=1}^i f_{i-j}g_j\)

直接给 FFT / NTT 套一个 cdq 就行,没啥难度。2log

void solve(int l, int r) {
	if (l == r) {if (!l) f[0] = 1; return;} int mid = (l + r) >> 1, _n = 0, _m = 0;
	solve(l, mid);
	for (int i = l, j = 0; i <= mid; i++, j++) a[j] = f[i], _n = j;
	for (int i = 0, j = 0; i <= r - l; i++, j++) b[j] = g[i], _m = j;

	int lim = 1, w = 0; while (lim <= r - l + 1) lim <<= 1, w++;
	for (int i = 0; i < lim; i++) rv[i] = (rv[i >> 1] >> 1) | ((i & 1) << (w - 1));
	for (int i = _n + 1; i < lim; i++) a[i] = 0; //remind: clear
	for (int i = _m + 1; i < lim; i++) b[i] = 0;
	ntt(lim, a, G), ntt(lim, b, G);
	for (int i = 0; i < lim; i++) a[i] = 1ll * a[i] * b[i] % mod;
	ntt(lim, a, iG);

	for (int i = mid + 1; i <= r; i++) (f[i] += a[i - l]) %= mod;
	solve(mid + 1, r);
}

\(\text{ }\)

\(\text{ }\)

多项式 inv

给定多项式 \(F(x)\),求一个多项式 \(G(x)\) 使得 \(F(x)G(x)\equiv1\pmod {x^n}\)

核心思想:倍增。

假设我们获取了 \(F(x)\bmod x^{n/2}\) 的逆元 \(G(x)\),要求 \(x^n\) 的逆元 \(H(x)\)

\[\begin{cases}F(x)G(x)\equiv1\pmod{x^{n/2}}\\F(x)H(x)\equiv1\pmod {x^n}\end{cases} \]

\[\begin{aligned} \implies G(x)-H(x)&\equiv0\pmod{x^{n/2}}\\ (G(x)-H(x))^2&\equiv0\pmod{x^n}\\ G^2(x)-2G(x)H(x)+H^2(x)&\equiv0\pmod{x^n}\\ F(x)G^2(x)-2F(x)G(x)H(x)+F(x)H^2(x)&\equiv0\pmod{x^n}\\ F(x)G^2(x)-2G(x)+H(x)&\equiv0\pmod{x^n} \end{aligned}\]

所以 \(H(x)=2G(x)-F(x)G^2(x)\),可以递归求解。

\(T(n)=T(n/2)+\Theta(n\log n)\),得算法复杂度 \(O(n\log n)\)

void solve(int n) {
	if (!n) return g[0] = qpow(f[0], mod - 2), void();
	solve(n >> 1);
	int lim = 1, w = 0; while (lim <= n * 2) lim <<= 1, w++;
	for (int i = 0; i < lim; i++) rv[i] = (rv[i >> 1] >> 1) | ((i & 1) << (w - 1));

	for (int i = 0; i <= n; i++) tf[i] = f[i], tg[i] = g[i];
	for (int i = n + 1; i < lim; i++) tf[i] = tg[i] = 0;
	ntt(lim, tf, G), ntt(lim, tg, G);
	for (int i = 0; i < lim; i++) tg[i] = ((2ll * tg[i] - 1ll * tf[i] * tg[i] % mod * tg[i]) % mod + mod) % mod;
	ntt(lim, tg, iG);
	for (int i = 0; i <= n; i++) g[i] = tg[i];
}

这个是递归函数,但是递归层数太少了所以对常数没影响。

\(\text{ }\)

\(\text{ }\)

多项式 ln

\(G(x)\equiv\ln F(x)\pmod{x^n}\)

核心思想:导完再积回去。

\[\begin{aligned}G(x)&\equiv \ln F(x)\pmod{x^n}\\G^{\prime}(x)&\equiv\frac{F^{\prime}(x)}{F(x)}\pmod{x^{n-1}}\\G(x)&\equiv\int\frac{F^{\prime}(x)}{F(x)}\text{d}x\pmod{x^n}\end{aligned} \]

多项式的求导与积分都是平凡的;除法就求逆完成即可。

void dev(int n, int A[]) {for (int i = 1; i <= n; i++) a[i - 1] = 1ll * i * a[i] % mod; a[n] = 0;}
void ved(int n, int A[]) {for (int i = n - 1; ~i; i--) a[i + 1] = 1ll * inv[i + 1] * a[i] % mod; a[0] = 0;}

//in main():
get_inv(a, ia, n); dev(n, a);
int lim = 1, w = 0; while (lim <= n * 2) lim <<= 1, w++;
for (int i = 0; i < lim; i++) rv[i] = (rv[i >> 1] >> 1) | ((i & 1) << (w - 1));
ntt(lim, a, G), ntt(lim, ia, G);
for (int i = 0; i < lim; i++) a[i] = 1ll * a[i] * ia[i] % mod;
ntt(lim, a, iG); ved(n, a);

\(\text{ }\)

\(\text{ }\)

多项式 exp

\(G(x)\equiv \text e^{F(x)}\pmod{x^n}\)

【警告⚠⚠⚠ 这里是垃圾 2log 做法 ⚠⚠⚠警告】

\[\begin{aligned} G(x)&\equiv \text e^{F(x)}\pmod{x^n} \\ G^{\prime}(x)&\equiv \text e^{F(x)}F^{\prime}(x)\pmod{x^n} \\ G^{\prime}(x)&\equiv G(x)F^{\prime}(x)\pmod{x^n} \\ G(x)&\equiv\int G(x)F^{\prime}(x)\text dx\pmod{x^n} \\ \end{aligned}\]

\[[x^n]G(x)=[x^n]\int G(x)F^{\prime}(x)\text dx=\dfrac1n[x^{n-1}]G(x)F^{\prime}(x) \]

就是半在线卷积的形式,直接分治 NTT,\(O(n\log^2n)\)。代码不放了。

其实很难不发现,套用这个做法前面两个 inv 与 ln 都能做,但是毕竟复杂度更劣,还是太嘟嘟了。

\(\text{ }\)

\(\text{ }\)

多项式 qpow

给定多项式 \(F(x)\),求 \(G(x)\) 满足 \(G(x)\equiv F(x)^k\pmod{x^n}\)

特殊情况:[x^0]F(x)=1

\(a_0=1\)

一个唐氏做法就是直接 qpow,是 \(O(n\log n\log k)\) 的。模板题 \(k\le10^{10^5}\),所以要稍微处理一下指数,直接对 \(p\) 取模即可,注意不是 \(\varphi(p)\)。然后再稍微减少一下 NTT 调用次数,就可以 1.5s 内通过了。

为什么可以令 \(k\gets k\bmod p\)
考虑证明 \(f(x)^p\equiv f(x^p)\pmod p\)。考虑归纳法,首先 \(a_0=1\),故零次多项式肯定成立;然后设当前的 \(f(x)\)\(n\) 次多项式,且其低 \(n-1\) 次多项式 \(g(x)\) 已经满足上述命题。提取出最高位 \(f(x)=g(x)+a_kx^k\),简单推推式子

\[f(x)^p\equiv\left(g(x)+a_kx^k\right)^p\equiv g(x)^p+a_k^px^{kp}\equiv g(x^p)+a_kx^{kp}=f(x^p) \]

有点跳步:1->2 定义,2->3 二项式定理拆开有用项,3->4 对应一下项显然。

注意这是 2log 的,比较菜。感觉不如:

核心思想:ln 完再 exp 回去。

\[\begin{aligned}G(x)&\equiv F(x)^k\pmod{x^n}\\\ln G(x)&\equiv k\ln F(x)\pmod{x^n}\\G(x)&\equiv \exp(k\ln F(x))\pmod{x^n}\end{aligned} \]

直接做,1log。我 exp 板子太烂是 2log 的

ln(n, lim);
for (int i = 0; i < n; i++) f[i] = 1ll * f[i] * k % mod;
for (int i = n; i <= lim; i++) f[i] = 0;
exp(n, lim);

\(\text{ }\)

任意情况

去除了 \(a_0=1\)

这个看起来没B用的限制,实则使得我们上面那个 \(k\gets k\bmod p\) 的证明失效了!!

而且你 ln 的一个保证也是 \(a_0=1\),也寄寄了!!


核心思想:强行使 \(a_0=1\)

首先若 \(a_0=0\),那就先一直找到最低的位置 \(a_t\ne0\),然后降次一下。即 \(F(x)^k=\left(\frac{F(x)}{x^t}\right)^k\times x^{tk}\)

那么此时默认 \(a_0\ne0\)。直接对多项式除以 \(a_0\) 即可。即 \(F(x)^k=\left(\frac{F(x)}{a_0}\right)^k\times a_0^k\)

然后就可以套用上面做法啦。注意一堆细节:

  • 若找不到 \(a_t\ne0\),直接 return 全零即可。
  • \(tk\ge n\),同样 return 全零。
  • 多项式指数 mod \(p\);数值的指数 mod \(p-1\)(比如此处的 \(a_0^k\))。
  • 可能要特判 \(k=0\)

code:

int n, k_m, k_m1; ll real_k;
void fuck() {
	char op = getchar(); while (op < 48 || op > 57) op = getchar();
	while (48 <= op && op <= 57) {
		k_m = (10ll * k_m + (op ^ 48)) % mod;
		k_m1 = (10ll * k_m1 + (op ^ 48)) % (mod - 1);
		real_k = min(10ll * real_k + (op ^ 48), (ll)1e10);
		op = getchar();
	}
}
int temp[N];
int main() {
	scanf("%d", &n), fuck();
	if (!real_k) {for (int i = 0; i < n; i++) printf("%d ", !i ? 1 : 0); return 0;}
	for (int i = 0; i < n; i++) scanf("%d", &f[i]);

	int t = 0; while (t < n && !f[t]) t++;
	if (t == n || (__int128)t * real_k >= n) {while (n--) printf("0 "); return 0;}
	for (int i = t; i < n; i++) temp[i - t] = f[i]; n -= t;
	for (int i = 0; i < n; i++) f[i] = temp[i];

	int val = qpow(f[0], k_m1), iv = qpow(f[0], mod - 2);
	for (int i = 0; i < n; i++) f[i] = 1ll * f[i] * iv % mod;

	inv[1] = 1; for (int i = 2; i <= n; i++) inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
	int lim = 1, w = 0; while (lim <= 2 * n) lim <<= 1, w++;
	for (int i = 0; i < lim; i++) rv[i] = (rv[i >> 1] >> 1) | ((i & 1) << (w - 1));

	ln(n, lim);
	for (int i = 0; i < n; i++) f[i] = 1ll * f[i] * k_m % mod;
	for (int i = n; i <= lim; i++) f[i] = 0;
	exp(n, lim);

	for (int _ = 1; _ <= t * real_k; _++) printf("0 ");
	for (int i = 0; i < n + t - t * real_k; i++) printf("%d ", 1ll * ans[i] * val % mod);
	return 0;
}

这个是傻逼洛谷模板题代码。因为太唐了我就不截取了,全部丢上来(

\(\text{ }\)

\(\text{ }\)

多项式 sqrt

给定 \(F(x)\),求 \(G(x)\) 满足 \(G(x)^2\equiv F(x)\pmod{x^n}\)

特殊情况:[x^0]F(x)=1

保证了 \(a_0=1\),直接当作 \(\frac12\) 次数按上面 qpow 来做即可。\(O(n\log n)\)

ln(n, lim);
for (int i = 0; i < n; i++) f[i] = 1ll * f[i] * i2 % mod;
for (int i = n; i <= lim; i++) f[i] = 0;
exp(n, lim);

\(\text{ }\)

任意情况

我不会二次剩余啊 /kk

\(\text{ }\)

\(\text{ }\)

多项式(带余)除法

给定多项式 \(F(x),G(x)\),求 \(Q(x),R(x)\) 满足 \(F(x)=G(x)Q(x)+R(x)\)

\(F,G,Q,R\) 多项式次数分别为 \(n,m,n-m+1,m-1\)。(洛谷模板题中 \(n,m\) 加了一)

核心思想:转置。

考虑一个 "多项式系数反转" 的操作,即 \([x^{k}]\mathcal A(x)=[x^{n-k}]A(x)\)。容易编出等价描述

\[\mathcal A(x)=x^n A(\frac 1x) \]

考虑算 \(Q\)。就把带余除法的式子对着这个转置做就行:

\[\begin{aligned} F(x) &= G(x)Q(x)+R(x) \\ F(\frac 1x) &= G(\frac 1x)Q(\frac 1x)+R(\frac 1x) \\ x^n F(\frac 1x) &= x^m G(\frac 1x) x^{n-m} Q(\frac 1x)+x^{n-m+1}x^{m-1} R(\frac 1x) \\ \mathcal F(x) &= \mathcal G(x)\mathcal Q(x)+x^{n-m+1}\mathcal R(x) \\ \mathcal F(x) &\equiv \mathcal G(x)\mathcal Q(x)\pmod{x^{n-m+1}} \\ \mathcal Q(x) &\equiv \frac{\mathcal F(x)}{\mathcal G(x)} \pmod{x^{n-m+1}} \end{aligned}\]

\(F,G\) 转置,\(G\) 求逆,算出 \(Q\) 的转置,再转回去得到 \(Q\),再直接算得到 \(R\)\(O(n\log n)\)

for (int i = 0; i < n; i++) fR[i] = f[n - i - 1];
for (int i = 0; i < m; i++) gR[i] = g[m - i - 1];
get_inv(n - m + 1, gR, inv_gR);

int lim = 1, w = 0; while (lim <= (n << 1)) lim <<= 1, w++;
for (int i = 0; i < lim; i++) rv[i] = (rv[i >> 1] >> 1) | ((i & 1) << (w - 1));
ntt(lim, fR, G), ntt(lim, inv_gR, G);
for (int i = 0; i < lim; i++) qR[i] = 1ll * fR[i] * inv_gR[i] % mod;
ntt(lim, qR, iG);
for (int i = n - m + 1; i < lim; i++) qR[i] = 0;
for (int i = 0; i < n - m + 1; i++) q[i] = qR[(n - m + 1) - i - 1];

ntt(lim, q, G), ntt(lim, g, G);
for (int i = 0; i < lim; i++) r[i] = 1ll * q[i] * g[i] % mod;
ntt(lim, q, iG), ntt(lim, r, iG);
for (int i = 0; i < m - 1; i++) r[i] = (f[i] - r[i] + mod) % mod;

\(\text{ }\)

\(\text{ }\)

下降幂多项式乘法

下降幂多项式形如 \(F(x)=\sum a_ix^{\underline i}\)。求 \(F(x)G(x)\) 得到的下降幂多项式。

对于多项式 \(F(x)\),定义其 点值 EGF

\[\hat F(x)=\sum\limits_{i\ge0}F(i)\dfrac{x^i}{i!} \]

我们在此代入 \(F(x)\) 的表达式,

\[\begin{aligned}\hat F(x) &= \sum\limits_{i\ge0}(\sum_{0\le j\le i} a_j i^{\underline j})\dfrac{x^i}{i!} \\ &= \sum\limits_{i\ge0}(\sum_{0\le j\le i} a_j\dfrac{i!}{(i-j)!})\dfrac{x^i}{i!} \\ &= \sum\limits_{0\le j\le i}a_j\dfrac{x^i}{(i-j)!} \\ &= \sum\limits_{j\ge0}a_j\cdot(\sum_{i\ge j}\dfrac{x^i}{(i-j)!}) \\ &= \sum\limits_{j\ge0}a_j\cdot(x^j\sum_{i\ge0}\dfrac{x^i}{i!}) \\ &= \sum a_j(x^j\text e^x) \\ &= \text e^x\sum a_jx^j \end{aligned}\]

也就是说,\(\hat F(x)\) 只需要用下降幂系数 \(a_i\) 对应的 OGF 乘上 \(\text e^x\) 即可得出。

注意 \(\hat F(x)\) 是点值,所以分别对 \(F,G\) 算出点值 EGF \(\hat F(x),\hat G(x)\),直接对应位相乘就能得到 \(H\) 的点值 EGF \(\hat H(x)\),然后逆变换(乘上 \(\text e^{-x}\))得到 \(H\)\(O(n\log n)\)

void o2e(int A[], int n) {
	for (int i = 0; i < n; i++) t[i] = ifac[i];
	mul(A, t, n, n);
	for (int i = 0; i < n; i++) A[i] = 1ll * A[i] * fac[i] % mod;
	for (int i = n; i < N; i++) A[i] = 0;
}
void e2o(int A[], int n) {
	for (int i = 0; i < n; i++) A[i] = 1ll * A[i] * ifac[i] % mod;
	for (int i = 0; i < n; i++) t[i] = (i % 2 == 0 ? ifac[i] : mod - ifac[i]);
	mul(A, t, n, n);
	for (int i = n; i < N; i++) A[i] = 0;
}

//in main()
o2e(a, n + m - 1), o2e(b, n + m - 1);
for (int i = 0; i < n + m - 1; i++) a[i] = 1ll * a[i] * b[i] % mod;
e2o(a, n + m - 1);

关于点值相乘:考虑 \(\hat F(x)\) 搞成 OGF 的形式的系数实际是 \(\frac{F(i)}{i!}\),我们 EGF 肯定是转回去基本形式 OGF 再算。直接对每一项乘上 \(i!\) 就得到了真正点值 \(F(i)\),现在可以直接对应相乘了;\(F(i)G(i)=H(i)\)。然后再给 \(H(i)\) 除以 \(i!\) 就得到了 \(\hat H(x)\) 的系数。

\(\text{ }\)

\(\text{ }\)

封装多项式

待测试,估计还会有问题的,别急。

Code
const int mod = 998244353, G = 3, iG = 332748118;
//todo: add "assert"
//todo: optimize space
namespace Shit {
	inline int qpow(int x, int y) {int ans = 1; while (y) {if (y & 1) ans = 1ll * ans * x % mod; x = 1ll * x * x % mod; y >>= 1;} return ans;}
	int lim, w; vector <int> rv;
	void init(int n) {
		lim = 1, w = 0; while (lim <= n) lim <<= 1, w++; rv.resize(lim);
		for (int i = 0; i < lim; i++) rv[i] = (rv[i >> 1] >> 1) | ((i & 1) << (w - 1));
	}
	struct poly {
		vector <int> A; int &operator[] (int x) {return A[x];}
		poly(int n = 0) {A.shrink_to_fit(), A = vector<int>(n, 0);} poly(vector <int> T): A(T) {}
		inline void maintain() {while (!A.empty() && A.back() == 0) A.pop_back();}
		inline int size() {return A.size();}

		inline void rev() {reverse(A.begin(), A.end());}
		inline void dev() {int n = A.size(); for (int i = 1; i < n; i++) A[i - 1] = 1ll * i * A[i] % mod; A.pop_back();}
		inline void ved() {int n = A.size(); A.push_back(0); for (int i = n - 1; ~i; i--) A[i + 1] = 1ll * qpow(i + 1, mod - 2) * A[i] % mod; A[0] = 0;}

		void ntt(int op) {
			A.resize(lim); for (int i = 0; i < lim; i++) if (i < rv[i]) swap(A[i], A[rv[i]]);
			for (int k = 1; k < lim; k <<= 1) for (int i = 0, wn = qpow(op, (mod - 1) / (k << 1)); i < lim; i += (k << 1)) for (int j = 0, w = 1; j < k; j++, w = 1ll * w * wn % mod) {int x = A[i + j], y = 1ll * w * A[i + j + k] % mod; A[i + j] = (x + y) % mod, A[i + j + k] = (x - y + mod) % mod;}
			if (op != G) {int inv = qpow(lim, mod - 2); for (int i = 0; i < lim; i++) A[i] = 1ll * A[i] * inv % mod;}
		}
		friend poly operator + (poly x, poly y) {int len = max(x.size(), y.size()); poly z(len); for (int i = 0; i < len; i++) z[i] = (x[i] + y[i]) % mod; return z;}
		friend poly operator - (poly x, poly y) {int len = max(x.size(), y.size()); poly z(len); for (int i = 0; i < len; i++) z[i] = (x[i] - y[i] + mod) % mod; return z;}
		friend poly operator * (poly x, poly y) {int len = x.size() + y.size() - 1; init(len); x.ntt(G), y.ntt(G); for (int i = 0; i < lim; i++) x[i] = 1ll * x[i] * y[i] % mod; x.ntt(iG), x.A.resize(len); return x;}
		friend poly operator / (poly f, poly g) { //O((n+m)log(n+m))
			f.maintain(), g.maintain();
			int n = f.size() - g.size() + 1; poly fR = f, gR = g; fR.rev(), gR.rev();
			gR.A.resize(n), gR.get_inv(); fR *= gR; fR.A.resize(n); fR.rev(); return fR;
		}
		// friend poly operator / (poly f, poly g) { //O(nm), use when m=O(1)
		// 	poly ans; f.maintain(), g.maintain();
		// 	for (int i = f.size() - 1, gsiz = g.size(); i >= gsiz - 1; i--) {
		// 		int k = 1ll * f[i] * qpow(g[gsiz - 1], mod - 2) % mod; ans.A.push_back(k);
		// 		for (int j = gsiz - 1, mi = i; ~j; j--, mi--) f[mi] = ((f[mi] - 1ll * k * g[j]) % mod + mod) % mod;
		// 	}
		// 	ans.rev(); return ans;
		// }
		friend poly operator % (poly f, poly g) {poly R = f - g * (f / g); R.A.resize(g.size() - 1); return R;} //O((n+m)log(n+m))
		void ksm(int M1, int M2, ll M3) { //M1=y%(mod-1), M2=y%mod, M3=min(y,10^10)
			int n = A.size();
			if (!M3) {for (int i = 0; i < n; i++) A[i] = !i ? 1 : 0; return;}
			int t = 0; while (t < n && !A[t]) t++; if (t == n || (__int128)t * M3 >= n) {A = vector<int>(n, 0); return;}
			for (int i = t; i < n; i++) A[i - t] = A[i]; A.resize(n -= t);
			int val = qpow(A[0], M1); poly F; *this /= A[0]; ln(), *this *= M2, exp();
			for (int i = 0; i < t * M3; i++) F.A.push_back(0);
			for (int i = 0; i < n + t - t * M3; i++) F.A.push_back(1ll * A[i] * val % mod);
			*this = F;
		}
		friend poly operator ^ (poly x, ll y) {x.ksm(y % (mod - 1), y % mod, y); return x;}
		friend poly& operator += (poly &x, const poly &y) {return x = x + y;}
		friend poly& operator -= (poly &x, const poly &y) {return x = x - y;}
		friend poly& operator *= (poly &x, const poly &y) {return x = x * y;}
		friend poly& operator /= (poly &x, const poly &y) {return x = x / y;}
		friend poly& operator %= (poly &x, const poly &y) {return x = x % y;}
		friend poly& operator ^= (poly &x, const ll &y) {return x = x ^ y;}

		friend poly operator * (poly x, int y) {for (int &v : x.A) v = 1ll * v * y % mod; return x;}
		friend poly operator / (poly x, int y) {y = qpow(y, mod - 2); for (int &v : x.A) v = 1ll * v * y % mod; return x;}
		friend poly& operator *= (poly &x, const int &y) {return x = x * y;}
		friend poly& operator /= (poly &x, const int &y) {return x = x / y;}

		void get_inv() { //mod x^n
			int Sn = A.size(); poly _A(1), B(1); B[0] = qpow(A[0], mod - 2);
			vector <int> vec; for (int i = Sn; i; i >>= 1) vec.push_back(i); reverse(vec.begin(), vec.end());
			for (int n : vec) {
				init(n << 1); _A.A.resize(n + 1); for (int i = 0; i <= n; i++) _A[i] = (i >= Sn ? 0 : A[i]);
				_A.ntt(G), B.ntt(G); for (int i = 0; i < lim; i++) B[i] = ((2ll * B[i] - 1ll * _A[i] * B[i] % mod * B[i]) % mod + mod) % mod; B.ntt(iG), B.A.resize(n + 1);
			}
			*this = B, A.resize(Sn);
		}
		void ln() { //mod x^n
			poly IA = *this; IA.get_inv(); dev(); init(A.size() << 1); *this *= IA; ved();
		}
		void __solve_for_exp(int l, int r, poly &B) {
			static poly _A, _B;
			if (l == r) {if (!l) B[0] = 1; else B[l] = 1ll * B[l] * qpow(l, mod - 2) % mod; return;}
			int mid = (l + r) >> 1; __solve_for_exp(l, mid, B);
			_A.A.clear(); for (int i = 0; i <= r - l; i++) _A.A.push_back(A[i]);
			_B.A.clear(); for (int i = l; i <= mid; i++) _B.A.push_back(B[i]);
			_B *= _A; for (int i = mid + 1; i <= r; i++) (B[i] += _B[i - l - 1]) %= mod;
			__solve_for_exp(mid + 1, r, B);
		}
		void exp() { //mod x^n
			int n = A.size(); poly B(n); dev(); __solve_for_exp(0, n - 1, B); *this = B;
		}
	};
	inline poly rev(poly A) {A.rev(); return A;}
	inline poly dev(poly A) {A.dev(); return A;}
	inline poly ved(poly A) {A.ved(); return A;}
	inline poly get_inv(poly A) {A.get_inv(); return A;}
	inline poly ln(poly A) {A.ln(); return A;}
	inline poly exp(poly A) {A.exp(); return A;}
} using Shit::poly;
posted @ 2025-05-31 17:55  liangbowen  阅读(0)  评论(0)    收藏  举报