多项式全家桶笔记整理(完善中)

Part 0 泰勒展开的推广&多项式牛顿迭代

§ 0.1 记号和约定

为了不在下文引起混淆,这里简述一下这篇文章使用的记号:

  • \(f(x)\) 或者 \(F(x)\) :一个形式幂级数,此处的 \(x\) 是一个形式记号。
  • \(F(f(x))\) :两个多项式的复合。
  • \(\mathcal{F}[f(x)]\) :一个自变量为多项式的函数。此处的 \(\mathcal{F}\) 的自变量是多项式,而不是实数。而 \(x\) 仍然是一个表示多项式系数位置的形式符号,没有实际意义。为了体现出它与复合函数的区别,这里使用方括号和手写体。
  • \(f'(x)\) :多项式 \(f(x)\)普通导数,就是高数教材中的导数定义,但是暂时忽略函数收敛与否。区别于下文会提到的形式导数。
  • \(\int f(x)\) :多项式 \(f(x)\)普通积分,含义同上。区别于下文中的形式积分。
  • \(f(x^m)\) :将多项式每一系数的下标翻 \(m\) 倍后得到的新多项式,并不是复合函数

接下来我们会讨论如何求出 \(\mathcal{F}[f(x)]\) 的导数,并尝试将其泰勒展开。

§ 0.2 混账,你导了甚么!

第一个遇到的问题就是:我们该如何定义 \(R[[x]]\) 上的导函数。

要定义导函数,就需要定义微分,同时微分建立在无穷小量上,而要定义无穷小量,就需要先定义极限

可惜的是,我等凡人实在是难以想象一个 \(R[[x]]\) 上的极限该怎么定义。所幸,我们可以耍耍小聪明,绕过这一个障碍。

观察一个以多项式为自变量的函数 \(\mathcal F[f(x)]\) ,在OI中,它的解析式一般也是人畜无害的多项式形式,长成这样:

\[\mathcal F[f(x)] = k_0 +k_1 \mathcal f(x)+k_2 \mathcal f^2(x) +\cdots +k_n \mathcal f^n(x) \]

因为这里不存在针对 \(f(x)\) 的代入求值,最多也就是关于 \(x\) 求导数,所以多项式 \(f(x)\) 的存在更加接近于一个 “常量” 而非 “函数”,又考虑到多项式们有和实数几乎完全一致的加减乘运算,我们可以直接把一个多项式 \(f(x)\) 视作一个实数

\[\mathcal F[\mathcal X] = k_0 +k_1 \mathcal X+k_2 \mathcal X^2 +\cdots +k_n \mathcal X^n \]

现在就是我们的小聪明了:直接照抄实数域上的导函数定义,捏一个 \(\mathcal F\) 的 “导函数” 出来!

\[\mathcal F'[\mathcal X] = k_1 +2k_2 \mathcal X + 3k_3 \mathcal X^2 +\cdots +nk_n \mathcal X^{n-1} \]

我们暂且把这个东西叫做 “形式导数”,尽管直接跳过了极限和微分,我们仍然可以由它开始一步步打通泰勒展开的定义。

§ 0.3 噫!好!我展了!

回顾高等数学,实数域中泰勒展开的由来无非是挑选一个点,强制令新函数在这一点上的各阶导数和原来的函数相等。而且泰勒展开还有一个特点:如果被展开的函数是一个多项式函数或者能用幂级数表达的函数,其正确性的证明可以完全不依靠微分和无穷小量来得出。

如果有人想看的话我就去补写一下证明qwq
现在实在是懒得搞了

那么,既然多项式环有着和实数几乎一致的加减乘(导数需要的三种运算)和数乘运算(泰勒展开需要的额外运算),那么就算把实数版本的证明中的 \(x\) 改为多项式而不是实数,证明也依然成立。因此没有理由相信泰勒展开在多项式环上不能用。

换句话说,只要有了导数的形式,对多项式函数和指对函数泰勒展开就是正确的。

以自然底数为底的指数、对数函数可以写成无穷项幂级数的形式,所以它们也可以包含在内。

如果被展开函数是无穷多项的多项式函数,那么的确会用到极限。

但是因为求极限的对象是无穷多个多项式的加和,取到极限的变量仍是一个实数,我们仍然不需要定义 \(R[[x]]\) 上的极限。

正好,我们的 \(\mathcal F\) 也有着多项式的形式,这就为我们创造了机会,可以再次跳过微分,直接捏出一个新的泰勒展开:

\[\mathcal F[\mathcal X] = \sum_{k=0}^{+\infty} \frac{ \mathcal F^{(k)} [\mathcal X_0] }{k!}(\mathcal X - \mathcal X_0)^k \]

接下来可以把 \(\mathcal X\) 换成我们更熟悉的多项式 \(f(x)\) .

\[\mathcal F[ f(x) ] = \sum_{k=0}^{+\infty} \frac{ \mathcal F^{(k)} [ f_0(x) ] }{k!}( f(x) - f_0(x) )^k \]

好了!我们做完了!

虽然可能不甚严谨,但是管他呢,用就完了。

这里有一篇博客提供了另一种理解:多项式牛顿迭代(Freopen@csdn.com)

简单来说,我们挑选所有能让 \(f_0(x)\) 收敛的实数 \(x\) ,把它代入 \(f_0(x)\) 可以求出一些值,于是就可以把 \(f_0(x)\) 视作一个实数变量,同时自然把 \(\mathcal F[f(x)]\) 变成了一个以实数变量 \(f_0(x)\) 为自变量的函数,对其强行进行泰勒展开一定是正确的。

接下来再对所有的能让 \(f(x)\) 收敛的实数 \(x\) 代入其中,由于 \(f(x)\)\(f_0(x)\) 的值域都为 \(R\) ,多项式表达式的差异可以大致等同于函数值(\(\mathcal F\) 函数的自变量)的差异。

综上,我们就得到了这种泰勒展开的正确性。

一般会使用 \(\frac{\text d}{\text df(x)}\mathcal F[f(x)]\) 来表示多项式为变量的函数的导数,但是这种写法会让人误以为所比的无穷小量是 “\(f(x)\) 的函数值” 而非 “\(f(x)\) 的解析式结构”。

而其实前后有着很大的区别,前者仍然是实数域上的导数,后者才是真正属于多项式环的微分。

不过笔者没有想出更好的记号来,下文中仍会沿用这种写法。

§ 0.4 开始迭代

多项式牛顿迭代可以解决这样的问题:

已知多项式 \(g(x)\) ,求满足这种条件的多项式 \(f(x)\),系数对 \(998244353\) 取模。

\[g(f(x)) \equiv 0 \pmod{x^n} \]

换句话说就是求零点。

这里多项式取模的意思就是只保留次数最低的 \(n\) 位,后面的东西不要了。

考虑用倍增实现。每一轮通过模 \(x^{\lceil \frac{n}{2} \rceil}\) 意义下的解 \(f_0(x)\) 得到模 \(x^n\) 意义下的解 \(f(x)\) .

第一轮中,我们需要求出 \(g(x)\) 在模意义下的根,这个单独处理。

如何迭代计算模 \(x^n\) 意义下的答案呢?我们可以用刚才推导出的泰勒展开,把 \(\mathcal G\)(把 \(g\) 视作一个自变量为多项式的函数)在 \(f_0(x)\) 处展开:

\[\mathcal G[f(x)] = \sum_{k=0}^{+\infty} \frac{ \mathcal G^{(k)}[f_0(x)] }{k!}(f(x) - f_0(x))^k \]

然后,观察到 \((f(x) - f_0(x))^k\)\(k>2\) 情况下前 \(n\) 位都是零,这样我们就可以舍弃掉后面的东西了

\[\begin{align*} &\mathcal G[f(x)] \equiv 0 &\pmod{x^n} \\ \Rightarrow & \sum_{k=0}^{+\infty} \frac{ \mathcal G^{(k)}[f_0(x)] }{k!}(f(x) - f_0(x))^k \equiv 0 &\pmod{x^n} \\ \Rightarrow & \mathcal G[f_0(x)] + \mathcal G'[f_0(x)] \times(f(x) - f_0(x))& \pmod{x^n} \\ \Rightarrow & \mathcal G[f_0(x)] + \mathcal G'[f_0(x)] f(x) - \mathcal G'[f_0(x)] f_0(x)& \pmod{x^n} \end{align*} \]

当然还有另一种方式理解为什么要用倍增实现:

普通的牛顿迭代每次可以把精度翻倍,那么对应到多项式上,每次确定的项数也会翻倍。

化简式子

\[\mathcal G[f_0(x)] + \mathcal G'[f_0(x)] f(x) - \mathcal G'[f_0(x)] f_0(x)\equiv 0 \pmod{x^n} \]

得到

\[f(x)\equiv f_0(x) - \frac{\mathcal G[f_0(x)]}{\mathcal G'[f_0(x)]}\pmod{x^n} \]

诶嘿,正好是经典牛顿迭代法的形式。难道不应该是吗

这就做完了,时间复杂度分析如下:

已知多项式求逆的复杂为 \(O(n\log n)\) 求导和加减的复杂度是 \(O(n)\) ,那么就有

\[T(n) = T(\frac{n}{2}) + O(n) + O(n\log n) = O(n\log n) \]

最终的复杂度是 \(O(n\log n)\) .

但是实际跑起来常数巨大无比。

Part 1 多项式乘法逆

§ 1.1 常规方法

在很多多项式算法(例如刚刚讲到的牛顿迭代)中都需要用一个多项式在模 \(x^n\) 意义下除以另一个多项式。这种操作等价于乘上另一个多项式的乘法逆元,现在我们来求这个东西。

根据刚才牛顿迭代的启发,我们用倍增解决这个问题,已知已经求得了模 \(x^{n/2}\) 意义下 \(f(x)\) 的逆元。

\[f_0^{-1}(x)f(x) \equiv 1 \pmod{x^{n/2}} \]

又因为目标答案长这样:

\[f^{-1}(x)f(x) \equiv 1 \pmod{x^{n/2}} \]

两式相减,得到:

\[f^{-1}(x) - f_0^{-1}(x) \equiv 0 \pmod{x^{n/2}} \]

式子两边平方,模数也平方,依然能整除:

\[f^{-2}(x) - 2 f_0^{-1}(x)f^{-1}(x) + f_0^{-2}(x) \equiv 0 \pmod{x^{n}} \]

然后式子把他自己收拾好了:

\[f^{-2}(x) \equiv f_0^{-1}(x) \big( 2f^{-1}(x) - f_0^{-1}(x) \big) \pmod{x^{n}} \]

两边同时乘上 \(f(x)\) :

\[f^{-1}(x) \equiv f_0^{-1}(x) \big( 2 - f(x) f_0^{-1}(x) \big) \pmod{x^{n}} \]

递归计算就做完了!采用本式计算乘法逆的话每轮会进行两次 NTT 和一次 INTT .

模板代码(含NTT)

namespace poly{
	const LL gn = 3;
	const LL gni = 332748118;
	int rev[maxn<<2],now_rev;
	void ReArrange(LL *F,int N)
	{
		if(now_rev != N)
		{
			rev[0] = 0; now_rev = N;
			for(int i=1; i<N; i++) rev[i] = (rev[i/2]/2) + (i&1)*N/2;
		}
		for(int i=0; i<N; i++) if(rev[i] > i) swap(F[rev[i]], F[i]);
		return;
	}

	void NTT(LL *F,int N,int inv)
	{
		ReArrange(F,N);
		for(int len=1; len<N; len<<=1)
		{
			LL stp = qpow((inv == 1) ? gn : gni, (mod-1)/(len<<1));
			for(int st=0; st<N; st+=(len<<1))
			{
				LL omega = 1;
				for(int i=0; i<len; i++)
				{
					LL U = F[st+i], V = M(F[st+len+i], omega);
					F[st+i] = madd(U,V); F[st+len+i] = msub(U,V);
					omega = M(omega,stp);
				}
			}
		}
		if(inv == -1)
		{
			LL Ninv = qpow(N,mod-2);
			for(int i=0; i<N; i++) F[i] = M(F[i],Ninv);
		}
		return;
	}

	LL pinv[maxn];
	void Inverse(LL *f1,int n)
	{
		int K = 1; while(K<n) {K = K<<1;} n = K;
		memset(tmp,0,sizeof(tmp));
		pinv[0] = qpow(f1[0],mod-2);//求出乘法逆元的第一项
		for(int len=2; len<=n; len<<=1)
		{
			K = len<<1;//两个多项式相乘后的长度
			memcpy(tmp, f1, sizeof(LL) * len); //临时复制过来f(x)
			memset(tmp+len, 0, sizeof(LL) * len); //后面补零
			NTT(tmp,K,1); NTT(pinv,K,1);
			for(int i=0; i<K; i++) pinv[i] = M(pinv[i], msub(2, M(tmp[i], pinv[i]))); //计算刚刚得出的式子
			NTT(pinv,K,-1);//变换回来
			memset(pinv+len, 0, sizeof(LL)*len); //取模,去掉多余的部分
			//此轮循环结束后,pinv[]中存储的由 f_0^{-1}(x) 变为 f^{-1}(x)
		}
		memcpy(f1, pinv, sizeof(LL)*n);
		return;
	}
}

§ 1.2 哇塞,这居然可以

既然我们有了牛顿迭代,为什么不用用它呢?

好!那么我们先构造出这么一个函数:

\[\mathcal F[g(x)] = \frac{1}{g(x)} - f(x) \]

可以看出来, \(\mathcal F\) 在模 \(x^n\) 意义下的零点就是我们要求的乘法逆元。

接下来列出牛顿迭代的式子:

\[g_{t+1}(x) \equiv g_{t}(x) - \frac{ \mathcal F[g_{t}(x)] }{ \mathcal F'[g_{t}(x)] } \pmod{x^n} \]

带入解析式:

\[g_{t+1}(x) \equiv g_{t}(x) - \frac{ \frac{1}{g_t(x)} - f(x) }{-\frac{1}{g^{2}_{t}(x)} } \pmod{x^n} \]

整理一下:

\[g_{t+1}(x) \equiv 2g_{t}(x) - g^{2}_{t}(x)f(x) \pmod{x^n} \]

所有带分母的项都消去了,可以用 NTT 计算了。

等等,这不就是上面的那个式子吗

数学真奇妙

Part 2 多项式除法(取模)

§ 2.1 十分神仙的技巧

多项式除法,就是给定 \(f(x),g(x)\) 求多项式 \(Q(x),R(x)\) ,使得

\[f(x) \equiv Q(x)g(x) + R(x) \pmod{x^n} \]

商和余数的英文分别叫 quotient 和 remainder,所以一般用 \(Q(x)\)\(R(x)\) 表示。

我们观察到,如果 \(R(x) \equiv 0 \pmod{x^n}\) 的话,只需要求逆就可以了。而对于 \(R(x) \not\equiv 0 \pmod{x^n}\) 的情况,我们需要想办法移除掉它的影响。

而移除的方法就是把 \(f(x),g(x)\) 的系数反向。

形式化地,构造变换

\[\bar f(x) = x^nf(\frac{1}{x}) \]

就可以把多项式的系数反向。

接下来对除法的式子进行这个变换(\(n=\deg f,m=\deg g\)

\[\begin{gather*} f(x) \equiv g(x) Q(x) + R(x) \pmod{x^n} \\ x^nf(\frac{1}{x}) \equiv x^ng(\frac{1}{x}) Q(\frac{1}{x}) + x^nR(\frac{1}{x})\pmod{x^n} \\ \end{gather*} \]

我们又知道,\(\deg Q = \deg f - \deg g = n-m\) 以及 \(\deg R \leq \deg g - 1\) ,所以就有

\[\begin{gather*} x^nf(\frac{1}{x}) \equiv \left(x^mg(\frac{1}{x})\right) \left(x^{n-m}Q(\frac{1}{x})\right) + x^nR(\frac{1}{x})\pmod{x^n} \\ \bar f(x) \equiv \bar g(x)\bar Q(x) + x^{n-m+1}\bar R(x)\pmod{x^n} \\ \end{gather*} \]

这个时候,奇妙的事情出现了,最后余数项 \(x^{n-m+1}R(x)\) 的最低次数为 \(n-m+1\) ,正好和最高次数为 \(n-m\) 的商 \(Q(x)\) 错开一位,我们把整个式子拿到模 \(x^{n-m}\) 意义下,就可以完全消去余数的影响:

\[\begin{gather*} \bar f(x) \equiv \bar g(x)\bar Q(x)\pmod{x^{n-m}} \\ {\bar f(x) \over \bar g(x) } \equiv \bar Q(x)\pmod{x^{n-m}} \\ \end{gather*} \]

现在除法变成了普通的多项式求逆,我们来总结一下步骤:

  1. \(f,g\) 反向。
  2. \(\bar f,\bar g\) 模上 \(x^{n-m}\) .
  3. 这个时候计算 \(\bar f(x) / \bar g(x)\) .
  4. 把结果模上 \(x^{n-m}\)反向,得到 \(Q(x)\) .
  5. 用结果乘一开始没有被反向、取模过的 \(g\) .
  6. 用一开始没有被反向、取模过的 \(f\) 减去上一步的乘积,得到 \(R(x)\) .

真搞不懂这个技巧是怎么想出来的=

模板代码(需要刚才的 NTT 和 Inverse)

	LL copy1[maxn],copy2[maxn];
	void Divide(LL *f1,LL *f2,int n1,int n2,LL *Q,LL *R)
	{
		memcpy(copy1,f1,sizeof(LL)*n1); memcpy(copy2,f2,sizeof(LL)*n2); //备份
		reverse(f1,f1+n1); reverse(f2,f2+n2); //两个乘数翻转
		int qlen = n1-n2+1; //模数的次数
		if(qlen < n2) memset(f2+qlen, 0, sizeof(LL) * (n2-qlen));

		Inverse(f2,qlen); //求除数的乘法逆元
		Multiply(f1,f2,qlen,qlen); //f1乘上f2的逆元
		memset(f1+qlen, 0, sizeof(LL) * qlen); //
		reverse(f1, f1+qlen); //乘积反转后就是商
		memcpy(Q, f1, sizeof(LL) * qlen); //因为下面要用到f1,这里先复制一遍

		memcpy(f2, copy2, sizeof(LL) * (n2)); //用copy2之前先把f2复制回来
		Multiply(copy2, f1, n2, qlen); //用除数乘上商
		for(int i=0; i<n2-1; i++) R[i] = msub(copy1[i],copy2[i]); //用被除数减去上一步的得数得到余数
		memcpy(f1, copy1, sizeof(LL) * n1); //最后把被除数复制回来
		return;
	}

Part 3 多项式指对函数

§ 2.1 指对函数的新定义

累了,直接上公式:

\[\begin{gather*} \exp f(x) = \sum_{k=0}^{+\infty}{\frac{1}{k!}f^k(x)} \\ \ln {(1+f(x))} = \sum_{k=0}^{+\infty}{\frac{(-1)^{n+1}}{k!}f^k(x)} \\ \end{gather*} \]

本质上还是之前的泰勒展开,其中对数函数因为 \(0\) 处无定义,选择在 \(1\) 处进行展开。

§ 2.2 导过去,积回来

已知多项式 \(f(x)\) ,求多项式 \(g(x)\) ,使得

\[g(x) = \ln(f(x)) \]

其中 \(ln\) 函数的定义如上。

如果直接按照新定义中的方法强行计算,我们会发现必须把全部的无穷项都计算出来才能得到正确答案,所以我们需要换一种方法。

为什么要计算无穷项?因为上面的定义式每多一项,多项式的每一个系数都会发生改变。

那就换一个方法吧。

既然把 \(\ln\) 函数化成初等形式只需要求导,那就对 \(\ln\) 关于 \(f\)形式导数,得到了这个式子:

\[\frac{\text d}{\text dx}\ln(f(x)) = \frac{1}{f(x)} \]

右边是很好计算的,那就积回去!

\[\int \frac{1}{f(x)} \,\text df(x)\, + C = \ln(f(x)) \]

问题出现了,我们无法关于 \(f(x)\) 积分 \(\frac{1}{f(x)}\) ,也不知道积分常数 \(C\) 应该是多少。

这该怎么解决呢?我们发现,刚刚求不出结果的原因是我们把 \(\ln\) 看做了一个以多项式为自变量的函数。

而现在我们来换一种思路:把 \(\ln\) 也看成一个特殊的多项式,把 \(\ln(f(x))\) 看成求 \(\ln(x)\)\(f(x)\) 这两个多项式的复合

这样,就可以关于实数 \(x\)\(ln(f(x))\)普通导数

\[\frac{\mathrm d}{\mathrm dx} \ln(f(x)) = \frac{1}{f(x)}f'(x) \]

仿照上面,用积分表示左边:

\[\ln(f(x)) = \int \frac{f'(x)}{f(x)} \,\text dx\,+ln(f(0)) \]

化简成功!

经验:多项式的操作十分灵活,既可以关于多项式求导(形式导数),也可以关于实数求导(普通导数),两种方法都可以用来化简表达式。

模板代码(需要上文的 Inverse)

	void Derivate(LL *F,int N)
	{
		for(int i=0; i<N-1; i++) F[i] = M(F[i+1],i+1);
		F[N-1] = 0;
		return;
	}

	void Intergrate(LL *F,int N)
	{
		for(int i=N-1; i>0; i--) F[i] = M(F[i-1],mcon::inv[i]);
		F[0] = 0;
		return;
	}

	LL tmpi[maxn<<1];
	void Ln(LL *F,int N)
	{
		memcpy(tmpi,F,sizeof(LL)*N);
		Inverse(tmpi,N);
		Derivate(F,N);
		Multiply(F,tmpi,N,N,1);
		Intergrate(F,N);
		return;
	}

§ 2.3 一切偷懒,终将绳之以法!

有一点需要特别注意,后面的这一部分是积分常数,因为这一项的存在,必须要求

\[[x^0]f(x) = 1 \]

这是为什么呢?

伟大的 OI-wiki 曾经曰过:

首先,对于多项式 \(f(x)\) ,若 \(\ln f(x)\) 存在,那么由其定义,其必须满足 \([x^0]f(x) = 1\)

不过 OI-wiki 并没有给出进一步的解释,这让我这个蒟蒻一头雾水:为什么没有符合规定的常数项就不能计算了?

然后我翻到另一篇文章如是说:

\([x^0]f(x)\neq 1\) ,那么结果的常数项不收敛。

这让我更加混乱了

实际上这几句话的意思是:若常数项不是 \(1\),那么得数的常数项将无法用有理数取模表示

具体来说,根据刚才的推导,得数的常数项应为

\[\ln ([x^0]f(x)) \]

而根据 \(e\) 的超越性,我们知道 \(y=\ln x\) 只有在 \(x=0\) 时才能取到有理数值,其余值均为无理数,也就没有办法取模。

这里有更加详细的证明:题解P4725(Kinesis@luogu.com.cn)

§ 2.4 多项式指数函数

已知多项式 \(f(x)\) ,求 \(g(x) = \exp(f(x))\) ,其中 \(\exp\) 函数的定义同 §2.4 。

这一部分十分容易推导,用牛顿迭代法即可。构造函数如下:

\[\mathcal F[g(x)] = \ln(g(x)) - f(x) \]

此函数的零点即为所求的 \(\exp(f(x))\) .

接下来使用牛顿迭代:

\[g_{t+1}(x) \equiv g_{t}(x) - \frac{ \mathcal F[g_{t}(x)] }{ \mathcal F'[g_{t}(x)] } \pmod{x^n} \]

由于 \(\mathcal F'[g(x)] = \frac{1}{g(x)}\) ,可得:

\[g_{t+1}(x) \equiv g_{t}(x) - g_{t}(x) \big( \ln(g_{t}(x)) - f(x) \big) \pmod{x^n} \]

\[g_{t+1}(x) \equiv g_{t}(x) \big(1 - \ln(g_{t}(x)) - f(x) \big) \pmod{x^n} \]

时间复杂度为 \(O(n\log n)\) .

模板代码(需要上文的 Ln)

	LL f1[maxn<<1],f2[maxn<<1];
	void Exp(LL *F,int N)
	{
		int K=1;
		while(K<N) K<<=1;
		memset(f1,0,sizeof(f1));
		memset(f2,0,sizeof(f2));
		f1[0] = 1;
		for(int len=2; len<=K; len<<=1)
		{
			int len2 = len<<1;
			memcpy(f2,f1,sizeof(LL)*len);
			Ln(f2,len);
			for(int i=0; i<len; i++) f2[i] = madd(msub(0,f2[i]),F[i]);
			f2[0]++;
			NTT(f1,len2,1); NTT(f2,len2,1);
			for(int i=0; i<len2; i++) f1[i] = M(f1[i],f2[i]);
			NTT(f1,len2,-1);
			memset(f1+len,0,sizeof(LL)*len);
		}
		memcpy(F,f1,sizeof(LL)*N);
	}

这里再来分析一下为什么每轮循环中需要把数组的长度开到 \(2\times \deg g_i(x)\) .

首先,我们知道在式子

\[g_{i}(x) \equiv g_{i-1}(x) \big(1 - \ln(g_{i-1}(x)) - f(x) \big) \pmod{x^{2^i}} \]

中,\(f(x)\) 高于 \(x^{2^i}\) 的项都是没有意义的,因为它最低的贡献也在 \(x^{2^i}\) 的位置,最终会全部被模掉。同理,\(\ln(g_{i-1}(x))\) 也只需要保留前 \(2^{i}\) 项。

对应到代码中,我们只需要把 F[x] 的前 len 项复制过来用以相乘,求 \(\ln(g_{i-1}(x))\) 时也只需要求到长度为 len 为止。

另外,外层用来相乘的 \(g_{i-1}(x)\) 的次数为 \(2^{i-1}\) ,所以最终结果(未取模)的最高次数是 \(1.5\times 2^{i}\) ,而为了凑齐进行 NTT 所需的整次幂,需要把长度补齐到 \(2\times 2^i\) .

对应到代码上,就是相乘是数组长度开到 len<<1 .

§ 2.5 多项式快速幂

Part 4 多项式开平方根

posted @ 2023-02-16 17:33  HMSF  阅读(73)  评论(0编辑  收藏  举报