多项式全家桶学习笔记【持续更新】

本文完成的时间跨度较长,文风变化可能较大……

最近更新于2020年2月17日

Part 1.主线

乘法

前面讲过FFT

然而FFT常数感人,适用范围还窄,比如不能取模

于是有了NTT

其实就是取模的FFT

FFT 需要用到复数 ω = c o s ( 2 π n ) + s i n ( 2 π n ) i \omega=cos(\frac{2\pi}{n})+sin(\frac{2\pi}{n})i ω=cos(n2π)+sin(n2π)i,因为具有循环卷积性质

ω n = 1 , ω k ( 1 ≤ k ≤ n ) \omega^n=1,\omega^k(1\leq k\leq n) ωn=1,ωk(1kn)互不相同

我们希望找一个模意义下的替代品

显然就是原根

具体地说, w ≡ g M O D − 1 n ( m o d M O D ) w\equiv g^{\frac{MOD-1}{n}}\pmod {MOD} wgnMOD1(modMOD)

所以模数 M O D MOD MOD需要满足

  1. 有原根
  2. M O D = p × 2 k + 1 MOD=p\times 2^k+1 MOD=p×2k+1,其中 2 k > n 2^k>n 2k>n

最常用的是 998244353 998244353 998244353

有时会用3个,就记 469762049 , 998244353 , 1004535809 469762049,998244353,1004535809 469762049,998244353,1004535809

这三个原根都是 3 3 3

什么?记不住?

那就记 p p p: 7 , 119 , 479 7,119,479 7,119,479,考场上拿计算器算一下就可以了

然后和 FFT 没啥差别

下面贴的是自己摸索出的最方便的版本,会持续更新。和后面的代码会有不同。

typedef long long ll;
inline int add(const int& x,const int& y){
     return x+y>=MOD? x+y-MOD:x+y;}
inline int dec(const int& x,const int& y){
     return x<y? x-y+MOD:x-y;}
inline int qpow(int a,int p)
{
     
	int ans=1;
	while (p)
	{
     
		if (p&1) ans=(ll)ans*a%MOD;
		a=(ll)a*a%MOD;p>>=1;
	}
	return ans;
}
#define inv(x) qpow(x,MOD-2)
int rt[2][24];
int l,lim,r[MAXN];
inline void init(){
     lim=1<<l;for (int i=0;i<lim;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));}
inline void NTT(int* a,int type)
{
     
	for (int i=0;i<lim;i++) if (i<r[i]) swap(a[i],a[r[i]]);
	for (int L=0;L<l;L++)
	{
     
		int mid=1<<L,len=mid<<1,Wn=rt[type][L+1];
		for (int s=0;s<lim;s+=len)
		{
     
			ll w=1;
			for (int k=0;k<mid;k++,w=w*Wn%MOD)
			{
     
				int x=a[s+k],y=w*a[s+mid+k]%MOD;
				a[s+k]=add(x,y);a[s+mid+k]=dec(x,y);
			}			
		}
	}
	if (type)
	{
     
		int t=inv(lim);
		for (int i=0;i<lim;i++) a[i]=(ll)a[i]*t%MOD;
	}
}
int main()
{
     
	rt[0][23]=qpow(3,119);rt[1][23]=inv(rt[0][23]);
	for (int i=22;i>=0;i--)
	{
     
		rt[0][i]=(ll)rt[0][i+1]*rt[0][i+1]%MOD;
		rt[1][i]=(ll)rt[1][i+1]*rt[1][i+1]%MOD;
	}
	/*do something*/
	return 0;
}

求逆

给定项数为 n n n的多项式 A A A,求 B B B使得 A × B ≡ 1 ( m o d   x n ) A \times B \equiv1(mod \text{ }x^n) A×B1(mod xn),系数对 998244353 取 模 998244353取模 998244353

A × B ≡ 1 ( m o d x n ) A \times B \equiv1\pmod {x^n} A×B1(modxn)


A × B ′ ≡ 1 ( m o d x ⌈ n 2 ⌉ ) A \times B' \equiv1\pmod {x^{\lceil \frac{n}{2}\rceil}} A×B1(modx2n)

两式相减

A × ( B − B ′ ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) A \times (B-B') \equiv0\pmod {x^{\lceil \frac{n}{2}\rceil}} A×(BB)0(modx2n)

假装 A A A不为0

B − B ′ ≡ 0 ( m o d x ⌈ n 2 ⌉ ) B-B' \equiv0\pmod {x^{\lceil \frac{n}{2}\rceil}} BB0(modx2n)

我们要求的是 x n x^n xn,所以平方一下

B 2 − 2 B B ′ + B ′ 2 ≡ 0 ( m o d x n ) B^2-2BB'+B'^2 \equiv0\pmod {x^n} B22BB+B20(modxn)

B B B不爽,乘个 A A A消掉

B − 2 B ′ + A B ′ 2 ≡ 0 ( m o d x n ) B-2B'+AB'^2 \equiv0\pmod {x^n} B2B+AB20(modxn)

B ≡ 2 B ′ − A B ′ 2 ( m o d x n ) B\equiv2B'-AB'^2 \pmod {x^n} B2BAB2(modxn)

然后就可以递归了

边界时 n = 1 n=1 n=1,直接求逆元

注意取模

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)常数跟LCT有的一拼

int a[MAXN],b[MAXN];
void getinv(int *A,int n)
{
     
	if (n==1) return (void)(b[0]=inv(A[0]));
	getinv(A,(n+1)>>1);
	int l=0;
	while ((1<<l)<(n<<1)) ++l;
	init(l);
	for (int i=0;i<n;i++) a[i]=A[i];
	for (int i=n;i<(1<<l);i++) a[i]=0;
	NTT(a,l,1);NTT(b,l,1);
	for (int i=0;i<(1<<l);i++) b[i]=(ll)b[i]*(MOD+2-(ll)a[i]*b[i]%MOD)%MOD;
	NTT(b,l,-1);
	for (int i=n;i<(1<<l);i++) b[i]=0;
}

对数函数

给定 F ( x ) F(x) F(x),求 G ( x ) ≡ ln ⁡ ( F ( x ) ) ( m o d   x n ) G(x) \equiv \ln(F(x))(mod\text{ }x^n) G(x)ln(F(x))(mod xn)。系数对 998244353 998244353 998244353取模

前置知识

求导和积分

自行百度

对数函数求导

f ( x ) = ln ⁡ ( x ) f(x)=\ln(x) f(x)=ln(x)

f ′ ( x ) = 1 x f'(x)=\frac{1}{x} f(x)=x1

多项式求导和积分

其实就是幂函数

f ( x ) = x n f(x)=x^n f(x)=xn

f ′ ( x ) = n x n − 1 f'(x)=nx^{n-1} f(x)=nxn1

积分是逆运算

∫ f ( x ) d x = 1 n + 1 x n + 1 \int f(x)dx=\frac{1}{n+1}x^{n+1} f(x)dx=n+11xn+1

所有项加起来即可

复合函数

h ( x ) = f ( g ( x ) ) h(x)=f(g(x)) h(x)=f(g(x))

h ′ ( x ) = f ′ ( g ( x ) ) g ′ ( x ) h'(x)=f'(g(x))g'(x) h(x)=f(g(x))g(x)

注意: f ( g ( x ) ) f(g(x)) f(g(x))的自变量是 g ( x ) g(x) g(x)

正文

推式子


f ( x ) = ln ⁡ ( x ) f(x)=\ln(x) f(x)=ln(x)

G ( x ) ≡ f ( F ( x ) ) ( m o d x n ) G(x) \equiv f(F(x)) \pmod {x^n} G(x)f(F(x))(modxn)

同时求导

G ′ ( x ) ≡ f ′ ( F ( x ) ) F ′ ( x ) ( m o d x n ) G'(x) \equiv f'(F(x))F'(x) \pmod {x^n} G(x)f(F(x))F(x)(modxn)

G ′ ( x ) ≡ F ′ ( x ) F ( x ) ( m o d x n ) G'(x) \equiv \frac{F'(x)}{F(x)} \pmod {x^n} G(x)F(x)F(x)(modxn)

求导求逆乘起来,然后求积分即可

注意取模

inline void deriv(int *a,int *b,int n)
{
     
	for (int i=0;i<n-1;i++) b[i]=(ll)a[i+1]*(i+1)%MOD;
	b[n-1]=0;
}
inline void integ(int *a,int *b,int n)
{
     
	for (int i=1;i<n;i++) b[i]=(ll)a[i-1]*inv(i)%MOD;
	b[0]=0;
}
int F[MAXN],f[MAXN],G[MAXN];
int main()
{
     
	int n;
	scanf("%d",&n);
	for (int i=0;i<n;i++) scanf("%d",&F[i]);
	deriv(F,f,n);getinv(F,n);
	int l=0;
	while ((1<<l)<(n<<1)) ++l;
	init(l);
	NTT(f,l,1);NTT(b,l,1);
	for (int i=0;i<(1<<l);i++) f[i]=(ll)f[i]*b[i]%MOD;
	NTT(f,l,-1);
	integ(f,G,n);
	for (int i=0;i<n;i++) printf("%d ",G[i]);
	return 0;
}

然而之前一直有个问题,多项式怎么取对数?取了对数至少是无理数吧,为什么还能取模?

抱着疑惑,我们不取模,手算一下:

F ( x ) = x + 1 F(x)=x+1 F(x)=x+1

F ′ ( x ) = 1 F'(x)=1 F(x)=1

G ′ ( x ) = F ′ ( x ) F ( x ) = 1 x + 1 G'(x)=\frac{F'(x)}{F(x)}=\frac{1}{x+1} G(x)=F(x)F(x)=x+11

G ( x ) = ln ⁡ ( x + 1 ) G(x)=\ln(x+1) G(x)=ln(x+1)

根本不是多项式

也就是说,取对数只是加了个 ln ⁡ \ln ln

只是为了便于后期处理,通过取模把这玩意映射成了多项式。

(准确地说,是把它泰勒展开成多项式然后丢掉了高位。关于泰勒展开的姿势马上就会讲到)

和有理数取模一个道理。

多项式指数函数

给定 F ( x ) F(x) F(x)(常数项为 0 0 0),求 G ( x ) ≡ e F ( x ) ( m o d x n ) G(x) \equiv e^{F(x)} \pmod {x^n} G(x)eF(x)(modxn)。系数对 998244353 998244353 998244353取模

前置知识

泰勒展开

对于一个不方便直接求值的函数 f f f,求某个位置的值 f ( x ) f(x) f(x)

用多项式来逼近,具体步骤是强制让每一阶导数都相等。记这个多项式为 G G G

取一点 x 0 x_0 x0

f ( x ) = G ( x ) = ∑ i = 0 ∞ f ( i ) ( x 0 ) i ! ( x − x 0 ) i f(x)=G(x)=\sum_{i=0}^{\infty}\frac{f^{(i)}(x_0)}{i!}(x-x_0)^i f(x)=G(x)=i=0i!f(i)(x0)(xx0)i

( f ( i ) f^{(i)} f(i)表示 f f f i i i阶导数)

网上资料很多,不再细讲

正文

G ( x ) ≡ e F ( x ) ( m o d x n ) G(x) \equiv e^{F(x)} \pmod {x^n} G(x)eF(x)(modxn)

取对数

ln ⁡ ( G ( x ) ) ≡ F ( x ) ( m o d x n ) \ln(G(x)) \equiv F(x) \pmod {x^n} ln(G(x))F(x)(modxn)

ln ⁡ ( G ( x ) ) − F ( x ) ≡ 0 ( m o d x n ) \ln(G(x)) -F(x) \equiv 0\pmod {x^n} ln(G(x))F(x)0(modxn)

现在要求 G ( x ) G(x) G(x)

f ( G ( x ) ) ≡ ln ⁡ ( G ( x ) ) − F ( x ) ( m o d x n ) f(G(x)) \equiv \ln(G(x)) -F(x)\pmod {x^n} f(G(x))ln(G(x))F(x)(modxn)

现在要求这玩意的零点(没错,零点是个多项式)

f ( G ( x ) ) ≡ 0 ( m o d x n ) f(G(x)) \equiv 0\pmod {x^n} f(G(x))0(modxn)

假设求出了 f ( G 0 ( x ) ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) f(G_0(x))\equiv 0\pmod {x^{\lceil \frac{n}{2}\rceil}} f(G0(x))0(modx2n)

f ( G ( x ) ) f(G(x)) f(G(x)) G 0 ( x ) G_0(x) G0(x)处泰勒展开

f ( G ( x ) ) = f ( G 0 ( x ) ) + f ′ ( G 0 ( x ) ) 1 ! ( G ( x ) − G 0 ( x ) ) + f ′ ′ ( G 0 ( x ) ) 2 ! ( G ( x ) − G 0 ( x ) ) 2 + … … f(G(x))=f(G_0(x))+\frac{f'(G_0(x))}{1!}(G(x)-G_0(x))+\frac{f''(G_0(x))}{2!}(G(x)-G_0(x))^2+\dots\dots f(G(x))=f(G0(x))+1!f(G0(x))(G(x)G0(x))+2!f(G0(x))(G(x)G0(x))2+

因为 G ( x ) ≡ G 0 ( x ) ( m o d x ⌈ n 2 ⌉ ) G(x) \equiv G_0(x)\pmod {x^{\lceil \frac{n}{2}\rceil}} G(x)G0(x)(modx2n)……

什么?为什么?

首先 f ( G ( x ) ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) f(G(x))\equiv 0\pmod {x^{\lceil \frac{n}{2}\rceil}} f(G(x))0(modx2n)

因为方程有唯一解(不然没有意义),所以 G ( x ) G(x) G(x) G 0 ( x ) G_0(x) G0(x)是一个东西 但是在模意义下,所以 G ( x ) G(x) G(x)需要取模

好继续

因为

G ( x ) ≡ G 0 ( x ) ( m o d x ⌈ n 2 ⌉ ) G(x) \equiv G_0(x)\pmod {x^{\lceil \frac{n}{2}\rceil}} G(x)G0(x)(modx2n)

所以 ( G ( x ) − G 0 ( x ) ) 2 ≡ 0 ( m o d x n ) (G(x)-G_0(x))^2\equiv0\pmod {x^n} (G(x)G0(x))20(modxn)

发现上面那个展开式后面全没了

所以

f ( G ( x ) ) ≡ f ( G 0 ( x ) ) + f ′ ( G 0 ( x ) ) ( G ( x ) − G 0 ( x ) ) ( m o d x n ) f(G(x))\equiv f(G_0(x))+f'(G_0(x))(G(x)-G_0(x))\pmod {x^n} f(G(x))f(G0(x))+f(G0(x))(G(x)G0(x))(modxn)

左边不是 0 0 0

G ( x ) ≡ G 0 ( x ) − f ( G 0 ( x ) ) f ′ ( G 0 ( x ) ) ( m o d x n ) G(x)\equiv G_0(x)-\frac{f(G_0(x))}{f'(G_0(x))}\pmod {x^n} G(x)G0(x)f(G0(x))f(G0(x))(modxn)

这就是传说中的多项式牛顿迭代,但好像没啥关系。

观察一下,我们需要求 f ′ f' f

f ( G ( x ) ) = ln ⁡ ( G ( x ) ) − F ( x ) f(G(x)) = \ln(G(x)) -F(x) f(G(x))=ln(G(x))F(x)

由于自变量是 G ( x ) G(x) G(x), F ( x ) F(x) F(x)就是常数

f ′ ( G ( x ) ) = 1 G ( x ) f'(G(x))=\frac{1}{G(x)} f(G(x))=G(x)1

代回去

G ( x ) ≡ G 0 ( x ) − ( l n ( G 0 ( x ) ) − F ( x ) ) G 0 ( x ) ( m o d x n ) G(x) \equiv G_0(x)-(ln(G_0(x))-F(x))G_0(x)\pmod {x^n} G(x)G0(x)(ln(G0(x))F(x))G0(x)(modxn)

G ( x ) ≡ G 0 ( x ) ( 1 − l n ( G 0 ( x ) ) + F ( x ) ) ( m o d x n ) G(x) \equiv G_0(x)(1-ln(G_0(x))+F(x))\pmod {x^n} G(x)G0(x)(1ln(G0(x))+F(x))(modxn)

然后就可以递归啦

边界: n = 1 n=1 n=1时, ln ⁡ ( G ( x ) ) \ln(G(x)) ln(G(x)) F ( x ) F(x) F(x)常数项相等

题目保证为 0 0 0,所以 G ( x ) = 1 G(x)=1 G(x)=1

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn) 常数感人

int f[MAXN];
void getexp(int* A,int* B,int n)
{
     
	if (n==1) return (void)(*B=1);
	getexp(A,B,(n+1)>>1);
	int l=0;
	while ((1<<l)<(n<<1)) ++l;
	init(l);
	getln(B,f,n);
	for (int i=0;i<n;i++) 
	{
     
		f[i]=A[i]-f[i];
		if (f[i]<0) f[i]+=MOD;
	}
	++f[0];
	for (int i=n;i<(1<<l);i++) B[i]=f[i]=0;
	NTT(B,l,1);NTT(f,l,1);
	for (int i=0;i<(1<<l);i++) B[i]=(ll)f[i]*B[i]%MOD;
	NTT(B,l,-1);
}

幂函数

给定 F ( x ) F(x) F(x)(常数项为 1 1 1),求 G ( x ) ≡ F k ( x ) ( m o d x n ) G(x) \equiv F^k(x)\pmod {x^n} G(x)Fk(x)(modxn)。系数模 998244353 998244353 998244353

G ( x ) ≡ F k ( x ) ( m o d x n ) G(x) \equiv F^k(x)\pmod {x^n} G(x)Fk(x)(modxn)

同时取对数

ln ⁡ ( G ( x ) ) ≡ k ln ⁡ ( F ( x ) ) ( m o d x n ) \ln(G(x)) \equiv k\ln(F(x))\pmod {x^n} ln(G(x))kln(F(x))(modxn)

求出 ln ⁡ \ln ln,乘以 k k k,再 exp ⁡ \exp exp回去

因为函数衔接的地方有很多细节,这里贴一个用到幂函数的代码,可供参考

#include 
#include 
#include 
#include 
#define MAXN 400005
using namespace std;
inline int read()
{
     
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
const int MOD=950009857;
typedef long long ll;
inline int add(const int& x,const int& y){
     return x+y>=MOD? x+y-MOD:x+y;}
inline int dec(const int& x,const int& y){
     return x<y? x-y+MOD:x-y;}
inline int qpow(int a,int p)
{
     
	int ans=1;
	while (p)
	{
     
		if (p&1) ans=(ll)ans*a%MOD;
		a=(ll)a*a%MOD;p>>=1;
	}
	return ans;
}
#define inv(x) qpow(x,MOD-2)
int rt[2][22];
int l,r[MAXN];
inline void init(){
     for (int i=0;i<(1<<l);i++) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));}
inline void NTT(int* a,int type)
{
     
	int lim=1<<l;
	for (int i=0;i<lim;i++) if (i<r[i]) swap(a[i],a[r[i]]);
	for (int L=0;L<l;L++)
	{
     
		int mid=1<<L,len=mid<<1,Wn=rt[type][L+1];
		for (int s=0;s<lim;s+=len)
			for (int k=0,w=1;k<mid;k++,w=(ll)w*Wn%MOD)
			{
     
				int x=a[s+k],y=(ll)a[s+mid+k]*w%MOD;
				a[s+k]=add(x,y),a[s+mid+k]=dec(x,y);
			}
	}
	if (type)
	{
     
		int t=inv(lim);
		for (int i=0;i<lim;i++) a[i]=(ll)a[i]*t%MOD;
	}
}
void getinv(int* A,int* B,int n)
{
     
	static int f[MAXN],t[MAXN];
	if (n==1) return (void)(*B=inv(*A));
	getinv(A,t,(n+1)>>1);
	l=0;
	while ((1<<l)<(n<<1)) ++l;
	init();
	for (int i=0;i<n;i++) f[i]=A[i];
	for (int i=n;i<(1<<l);i++) f[i]=t[i]=0;
	NTT(f,0);NTT(t,0);
	for (int i=0;i<(1<<l);i++) B[i]=(ll)t[i]*dec(2,(ll)f[i]*t[i]%MOD)%MOD;
	NTT(B,1);
	for (int i=n;i<(1<<l);i++) B[i]=0;
}
inline void deriv(int* A,int* B,int n){
     for (int i=0;i<n-1;i++) B[i]=(ll)A[i+1]*(i+1)%MOD;B[n-1]=0;}
inline void integ(int* A,int* B,int n){
     for (int i=1;i<n;i++) B[i]=(ll)A[i-1]*inv(i)%MOD;B[0]=0;}
void getln(int* A,int* B,int n)
{
     
	static int f[MAXN],g[MAXN];
	deriv(A,f,n);getinv(A,g,n);
	for (int i=n;i<(1<<l);i++) f[i]=g[i]=0;
	NTT(f,0);NTT(g,0);
	for (int i=0;i<(1<<l);i++) f[i]=(ll)f[i]*g[i]%MOD;
	NTT(f,1);
	integ(f,B,n);
	for (int i=n;i<(1<<l);i++) B[i]=0;
}
void getexp(int* A,int* B,int n)
{
     
	static int f[MAXN],g[MAXN];
	if (n==1) return (void)(*B=1);
	getexp(A,g,(n+1)>>1);
	getln(g,f,n);
	for (int i=0;i<n;i++) f[i]=dec(A[i],f[i]);
	++f[0];
	for (int i=n;i<(1<<l);i++) f[i]=g[i]=0;
	NTT(f,0);NTT(g,0);
	for (int i=0;i<(1<<l);i++) B[i]=(ll)f[i]*g[i]%MOD;
	NTT(B,1);
	for (int i=n;i<(1<<l);i++) B[i]=0;
}
int a[MAXN],t[MAXN];
int main()
{
     
	rt[0][21]=qpow(5,453);rt[1][21]=inv(rt[0][21]);
	for (int i=20;i>=0;i--)
	{
     
		rt[0][i]=(ll)rt[0][i+1]*rt[0][i+1]%MOD;
		rt[1][i]=(ll)rt[1][i+1]*rt[1][i+1]%MOD;
	}
	/*略*/
	return 0;
} 

分治NTT

首先,洛谷上分治NTT板子其实没啥用……

更常用的是类似于

∏ i = 1 n ( x + a i ) \prod_{i=1}^n(x+a_i) i=1n(x+ai)

解决的方法是分成两半递归计算,然后一次NTT乘起来 复杂度是 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

注意这个算法的核心并不是减少NTT次数(NTT次数仍然是 n − 1 n-1 n1)而是在NTT的时候控制两边多项式的长度,使它是成倍增加的,这样短的多项式所耗的时间在长多项式面前不值一提。

所以在回溯到上一层之后会正向执行NTT,但你在这一层还是要反向NTT回去,因为两层NTT的长度不同。如果贸然使用上一层的长度会让复杂度退化。

还有临时数组必须每次递归单独开,并且长度要和区间长度相关。(但一般都不是区间长度)

给份伪代码

void solve(int l,int r,int F[])
{
     
	if (l==r) get(F,l),return;//将当前一位的值给F,返回
	int L[(r-l+1)<<1],R[(r-l+1)<<1];
	solve(l,mid,L),solve(mid+1,r,R);
	NTT(L),NTT(R);
	F=L*R;
	INTT(F);
}

几个经验

  • 各个函数会来回调用,不建议共用临时数组。可以在函数里面开static避免重名。
  • 在 NTT 前后取模可以万无一失
  • 控制一下语句顺序可以只在求逆的时候预处理 NTT

Part2.毒瘤线

除法

给定 F , G F,G F,G,求 Q , R Q,R Q,R使得 F = Q G + R F=QG+R F=QG+R,其中 d e g ( R ) < d e g ( G ) deg(R)deg(R)<deg(G)。系数对 998244353 998244353 998244353取模。

如果能去掉余数,就可以一波逆元算过去,可惜去不得。

所以考虑把 R R R模掉

F r ( x ) F_r(x) Fr(x)表示 F ( x ) F(x) F(x)系数翻转

显然 F r ( x ) = x n F ( 1 x ) F_r(x)=x^nF(\frac{1}{x}) Fr(x)=xnF(x1)( n n n为最高项次数)

推式子

F ( x ) = Q ( x ) G ( x ) + R ( x ) F(x)=Q(x)G(x)+R(x) F(x)=Q(x)G(x)+R(x)

F ( 1 x ) = Q ( 1 x ) G ( 1 x ) + R ( 1 x ) F(\frac{1}{x})=Q(\frac{1}{x})G(\frac{1}{x})+R(\frac{1}{x}) F(x1)=Q(x1)G(x1)+R(x1)

两边同时乘以 x n x^n xn

x n F ( 1 x ) = x n − m Q ( 1 x ) x m G ( 1 x ) + x n − m + 1 x m − 1 R ( 1 x ) x^nF(\frac{1}{x})=x^{n-m}Q(\frac{1}{x})x^mG(\frac{1}{x})+x^{n-m+1}x^{m-1}R(\frac{1}{x}) xnF(x1)=xnmQ(x1)xmG(x1)+xnm+1xm1R(x1)

F r ( x ) = Q r ( x ) G r ( x ) + x n − m + 1 R r ( x ) F_r(x)=Q_r(x)G_r(x)+x^{n-m+1}R_r(x) Fr(x)=Qr(x)Gr(x)+xnm+1Rr(x)

蛤蛤蛤

F r ( x ) ≡ Q r ( x ) G r ( x ) ( m o d x n − m + 1 ) F_r(x) \equiv Q_r(x)G_r(x)\pmod {x^{n-m+1}} Fr(x)Qr(x)Gr(x)(modxnm+1)

求一波逆算出 Q Q Q,再在原式中算出 R R R

int main()
{
     
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=0;i<=n;i++) scanf("%d",&F[i]);
	for (int i=0;i<=m;i++) scanf("%d",&G[i]);
	reverse(F,F+n+1);reverse(G,G+m+1);
	getinv(G,n-m+1);
	int l=0;
	while ((1<<l)<(n<<1)) ++l;
	init(l);
	memcpy(t,F,sizeof(t));
	for (int i=n-m+2;i<=n;i++) F[i]=0;
	NTT(F,l,1);NTT(b,l,1);
	for (int i=0;i<(1<<l);i++) Q[i]=(ll)F[i]*b[i]%MOD;
	NTT(Q,l,-1);
	memcpy(F,t,sizeof(F));
	for (int i=n-m+1;i<=n+m;i++) Q[i]=0;
	reverse(F,F+n+1);reverse(G,G+m+1);reverse(Q,Q+n-m+1);
	NTT(F,l,1);NTT(G,l,1);NTT(Q,l,1);
	for (int i=0;i<(1<<l);i++) R[i]=(MOD+F[i]-(ll)Q[i]*G[i]%MOD)%MOD;
	NTT(R,l,-1);NTT(Q,l,-1);
	for (int i=0;i<=n-m;i++) printf("%d ",Q[i]);
	puts("");
	for (int i=0;i<=m-1;i++) printf("%d ",R[i]);
	return 0;
}

多项式取模

除法算出来再乘回去减一下就可以了

多点求值

给一个多项式 F ( x ) F(x) F(x),给出 a 1 , a 2 , . . . , a m a_1,a_2,...,a_m a1,a2,...,am,求所有 F ( a i ) F(a_i) F(ai)

n , m ≤ 64000 n,m\leq64000 n,m64000

显然肯定不能一个一个求,因为你系数都遍历不完

所以多半是分治

对于一个区间 [ L , R ] [L,R] [L,R],求出多项式

G ( x ) = ∏ L R ( x − a i ) G(x)=\prod_{L}^{R}(x-a_i) G(x)=LR(xai)
F ( x ) F(x) F(x)除以 G ( x ) G(x) G(x),即 F ( x ) = D ( x ) G ( x ) + R ( x ) F(x)=D(x)G(x)+R(x) F(x)=D(x)G(x)+R(x)

代入 a i a_i ai,我们发现 G ( a i ) = 0 G(a_i)=0 G(ai)=0,所以 F ( a i ) = R ( a i ) F(a_i)=R(a_i) F(ai)=R(ai)

R R R的规模每次减半,所以可以递归了。

复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

具体实现的时候要先分治一次,用类似线段树的结构把所有用到的区间的 G G G存起来

这里开了 n n n个vector,后面的快速插值中用了一个指针数组动态申请内存……

常数极大,建议递归到较小长度的时候暴力秦九韶

闲着没事别在考场上写……

#include 
#include 
#include 
#include 
#include 
#include 
#define MAXN 262144
#define re register 
#define MOD 998244353
using namespace std;
typedef long long ll;
inline int qpow(int a,int p)
{
     
	int ans=1;
	while (p)
	{
     
		if (p&1) ans=(ll)ans*a%MOD;
		a=(ll)a*a%MOD;p>>=1;
	}
	return ans;
}
namespace In  
{
     
# define In_Len 2000000
    static std :: streambuf* fb ( std :: cin.rdbuf ( ) ) ;
    static char buf [In_Len], *ss ( 0 ) ;

    void In_init ( )  {
       fb -> sgetn ( ss = buf, In_Len ) ;  }

    inline int read ( )  {
     
        register int x ;
        bool opt ( 1 ) ;
        while ( isspace ( *ss ) )  ++ ss ;
        if ( *ss == 45 )  {
      ++ ss ; opt = 0 ; }
        for ( x = -48 + *ss ; isdigit ( * ++ ss ) ; ( x *= 10 ) += *ss - 48 ) ; ++ ss ;
        return opt ? x : -x ;
    }
# undef In_Len
}
using namespace In;
namespace Out  
{
     
	# define Out_Len 2000000
    static std :: streambuf* fb ( std :: cout.rdbuf ( ) ) ;
    static char buf [Out_Len], *ss ( buf ) ;
    inline void write ( register int x )  {
     
        static int T [30], tp ( 0 ) ;
        if ( ! x )  {
       *ss ++ =  48 ; *ss ++ = 10 ; return ;  }
        if ( x < 0 )  {
       *ss ++ = 45 ; x = -x ;  }
        while ( x ) T [++ tp] = x % 10 | 48, x /= 10 ;
        while ( tp )  *ss ++ = T [tp --] ;
        *ss ++ = 10 ;
    }
    inline void flush ( )  {
       fb -> sputn ( buf, ss - buf ) ;  }
# undef Out_Len
}
using namespace Out; 
inline int add(const int& x,const int& y){
     return x+y>=MOD? x+y-MOD:x+y;}
inline int dec(const int& x,const int& y){
     return x<y? x-y+MOD:x-y;}
#define inv(x) qpow(x,MOD-2)
int r[MAXN],rt[2][24];
inline void init(int l){
     for (re int i=0;i<(1<<l);++i) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));}
void NTT(int* a,int l,int type)
{
     
	type=(1-type)>>1;
	int lim=1<<l;
	for (re int i=0;i<lim;++i) if (i<r[i]) a[i]^=a[r[i]]^=a[i]^=a[r[i]];
	for (re int L=0;L<l;++L)
	{
     
		int mid=1<<L,len=mid<<1;
		int Wn=rt[type][L+1];
//		int Wn=qpow(3,(MOD-1)/(mid<<1));
//		if (type==-1) Wn=inv(Wn);
		for (re int s=0;s<lim;s+=len)
			for (re ll w=1,k=0;k<mid;++k,w=w*Wn%MOD)
			{
     
				const ll x=a[s+k],y=w*a[s+mid+k]%MOD;
				a[s+k]=add(x,y);a[s+mid+k]=dec(x,y);
			}
	}
	if (type)
	{
     
		int t=inv(lim);
		for (int i=0;i<lim;++i) a[i]=(ll)a[i]*t%MOD;
	}
}
void getinv(int* A,int* B,int n)
{
     
	static int t[MAXN];
	if (n==1) return (void)(*B=inv(*A));
	getinv(A,B,(n+1)>>1);
	int l=0;
	while ((1<<l)<(n<<1)) ++l;
	init(l);
	for (re int i=0;i<n;++i) t[i]=A[i];
	for (re int i=n;i<(1<<l);++i) t[i]=B[i]=0;
	NTT(t,l,1);NTT(B,l,1);
	for (re int i=0;i<(1<<l);++i) B[i]=(ll)B[i]*(MOD+2-(ll)B[i]*t[i]%MOD)%MOD;
	NTT(B,l,-1);
	for (re int i=n;i<(1<<l);++i) B[i]=0;
}
void getmod(int* A,int* B,int *R,int n,int m)
{
     
	static int f[MAXN],g[MAXN],d[MAXN],t[MAXN];
	for (re int i=0;i<=n;++i) f[i]=A[n-i];
	for (re int i=0;i<=m;++i) g[i]=B[m-i];
	getinv(g,t,n-m+1);
	int l=0;
	while ((1<<l)<=n*2) ++l;
	init(l);
	for (re int i=n-m+1;i<(1<<l);++i) f[i]=t[i]=0;
	NTT(f,l,1);NTT(t,l,1);
	for (re int i=0;i<(1<<l);++i) d[i]=(ll)f[i]*t[i]%MOD;
	NTT(d,l,-1);
	for (re int i=n-m+1;i<(1<<l);++i) d[i]=0;
	for (re int i=0;i<=n;++i) f[i]=A[i];
	for (re int i=0;i<=m;++i) g[i]=B[i];
	for (re int i=n+1;i<(1<<l);++i) f[i]=0;
	for (re int i=m+1;i<(1<<l);++i) g[i]=0;
	for (re int l=0,r=n-m;l<r;d[l]^=d[r]^=d[l]^=d[r],++l,--r);
	NTT(d,l,1);NTT(g,l,1);
	for (re int i=0;i<(1<<l);++i) R[i]=(ll)d[i]*g[i]%MOD;
	NTT(R,l,-1);
	for (re int i=0;i<m;++i) R[i]=(f[i]+MOD-R[i])%MOD;
	for (re int i=m;i<(1<<l);++i) R[i]=0;
}
int x[MAXN];
#define lc p<<1
#define rc p<<1|1
vector<int> v[MAXN<<2];
void cdqNTT(int p,int l,int r)
{
     
	v[p].resize(r-l+2);
	if (l==r) return (void)(v[p][0]=MOD-x[l],v[p][1]=1);
	const int mid=(l+r)>>1;
	cdqNTT(lc,l,mid);cdqNTT(rc,mid+1,r);
	int len=0;
	while ((1<<len)<=(r-l+1)) ++len;
	init(len);
	int L[1<<len],R[1<<len];
	for (re int i=0;i<=mid-l+1;++i) L[i]=v[lc][i];
	for (re int i=0;i<=r-mid;++i) R[i]=v[rc][i];	
	for (re int i=mid-l+2;i<(1<<len);++i) L[i]=0;
	for (re int i=r-mid+1;i<(1<<len);++i) R[i]=0;
	NTT(L,len,1);NTT(R,len,1);
	for (re int i=0;i<(1<<len);++i) L[i]=(ll)L[i]*R[i]%MOD;
	NTT(L,len,-1);
	for (re int i=0;i<=(r-l+1);++i) v[p][i]=L[i];
//	delete(L);delete(R);
}
int n,m;
int ans[MAXN];
void solve(int p,int l,int r,int* f)
{
     
	if (r-l<=850)
	{
     
		for (re int i=l;i<=r;++i)
			for (re int j=0,w=1;j<=r-l;++j,w=(ll)w*x[i]%MOD)
				ans[i]=(ans[i]+(ll)f[j]*w)%MOD;
		return;
	}
//	if (l==r) return (void)(ans[l]=*f);
	const int mid=(l+r)>>1;
	int cur[(r-l+1)<<2],L[(r-l+1)<<2];
	memset(cur,0,sizeof(cur));
	for (re int i=0;i<=mid-l+1;++i) cur[i]=v[lc][i];
	getmod(f,cur,L,r-l,mid-l+1);
	solve(lc,l,mid,L);
	for (re int i=0;i<=r-mid;++i) cur[i]=v[rc][i];
	for (re int i=r-mid+1;i<=mid-l+1;++i) cur[i]=0;
	getmod(f,cur,L,r-l,r-mid);	
	solve(rc,mid+1,r,L);
}
int f[MAXN],t[MAXN],a[MAXN];
int main()
{
     
	In_init();	
	rt[0][23]=qpow(3,119);rt[1][23]=inv(rt[0][23]);
	for (int i=22;i>=0;--i) rt[0][i]=(ll)rt[0][i+1]*rt[0][i+1]%MOD,rt[1][i]=(ll)rt[1][i+1]*rt[1][i+1]%MOD;
	n=read();m=read();
	for (re int i=0;i<=n;++i) f[i]=read();
	for (re int i=1;i<=m;++i) x[i]=read();
	cdqNTT(1,1,m);
	if (n>=m)
	{
     
		for (re int i=0;i<=m;++i) t[i]=v[1][i];
		memcpy(a,f,sizeof(a));
		getmod(a,t,f,n,m);
	}
	solve(1,1,m,f);
	for (re int i=1;i<=m;++i) write(ans[i]);
	flush();
	return 0;
}

快速插值

给定 n n n个点 ( x i , y i ) (x_i,y_i) (xi,yi),求一个过这 n n n个点的 n − 1 n-1 n1次多项式

x x x互不相同

考虑拉格朗日插值

f ( x ) = ∑ i = 1 n y i ∏ i ≠ j x − x j x i − x j f(x)=\sum_{i=1}^ny_i\prod_{i\neq j}\frac{x-x_j}{x_i-x_j} f(x)=i=1nyii=jxixjxxj

分式看着很烦,把它拆开

f ( x ) = ∑ i = 1 n y i ∏ i ≠ j ( x i − x j ) ∏ i ≠ j ( x − x j ) f(x)=\sum_{i=1}^n{ {y_i}\over{\prod_{i\neq j}(x_i-x_j)}}\prod_{i\neq j}(x-x_j) f(x)=i=1ni=j(xixj)yii=j(xxj)

考虑左边这坨怎么求

y i ∏ i ≠ j ( x i − x j ) { {y_i}\over{\prod_{i\neq j}(x_i-x_j)}} i=j(xixj)yi

上面是个常数,所以要求下面

我们设

g ( x ) = ∏ i = 1 n ( x − x i ) g(x)=\prod_{i=1}^n(x-x_i) g(x)=i=1n(xxi)

那么

∏ i ≠ j ( x − x j ) \prod_{i\neq j}(x-x_j) i=j(xxj)

可以写成

g ( x i ) x − x i \frac{g(x_i)}{x-x_i} xxig(xi)

代入 x = x i x=x_i x=xi得到……

哎怎么分母是 0 0 0

仔细算了一下,发现上面也是 0 0 0

这时候就要考虑洛必达法则了

因为 x → 0 x\to0 x0的时候上下同时趋近于 0 0 0,它们的比值等于它们导数的比值

所以等于

g ′ ( x i ) g'(x_i) g(xi)

代回原式

f ( x ) = ∑ i = 1 n y i g ′ ( x i ) ∏ i ≠ j ( x − x j ) f(x)=\sum_{i=1}^n{ {y_i}\over{g'(x_i)}}\prod_{i\neq j}(x-x_j) f(x)=i=1ng(xi)yii=j(xxj)

我们先分治算出 g ( x ) g(x) g(x),求个导算出 g ′ ( x ) g'(x) g(x),然后多 点 求 值算出所有 g ′ ( x i ) g'(x_i) g(xi),相当于是常数,我们记

y i ′ = y i g ′ ( x i ) y'_i={ {y_i}\over{g'(x_i)}} yi=g(xi)yi

f ( x ) = ∑ i = 1 n y i ′ ∏ i ≠ j ( x − x j ) f(x)=\sum_{i=1}^ny'_i\prod_{i\neq j}(x-x_j) f(x)=i=1nyii=j(xxj)

当然如果 x x x是连续正整数或者有其他一些奇妙的性质,你也许可以直接用阶乘之类的算出 y i ′ y'_i yi

现在考虑这个怎么求

考虑分治

f l , r ( x ) = ∑ i = l r y i ′ ∏ i = l , i ≠ j r ( x − x j ) f_{l,r}(x)=\sum_{i=l}^ry'_i\prod_{i=l,i\neq j}^r(x-x_j) fl,r(x)=i=lryii=l,i=jr(xxj)

mid=(l+r)>>1

考虑 f l , m i d ( x ) f_{l,mid}(x) fl,mid(x),它并没有计算 ∏ i ≠ j ( x − x j ) \prod_{i\neq j}(x-x_j) i=j(xxj) [ m i d + 1 , r ] [mid+1,r] [mid+1,r]的部分,但是因为 i i i在左边,右边没有缺口,所以直接乘上 ∏ i = m i d + 1 r ( x − x i ) \prod_{i=mid+1}^r(x-x_i) i=mid+1r(xxi)即可

同理可得

f l , r ( x ) = f l , m i d ( x ) g m i d + 1 , r ( x ) + g l , m i d ( x ) f m i d + 1 , r ( x ) f_{l,r}(x)=f_{l,mid}(x)g_{mid+1,r}(x)+g_{l,mid}(x)f_{mid+1,r}(x) fl,r(x)=fl,mid(x)gmid+1,r(x)+gl,mid(x)fmid+1,r(x)

递归即可,复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

瓶颈是多点求值,各种意义上

#include 
#include 
#include 
#include 
#include 
#define MAXN 524288
using namespace std;
namespace In  
{
     
# define In_Len 2000000
    static std :: streambuf* fb ( std :: cin.rdbuf ( ) ) ;
    static char buf [In_Len], *ss ( 0 ) ;

    void In_init ( )  {
       fb -> sgetn ( ss = buf, In_Len ) ;  }

    inline int read ( )  {
     
        register int x ;
        bool opt ( 1 ) ;
        while ( isspace ( *ss ) )  ++ ss ;
        if ( *ss == 45 )  {
      ++ ss ; opt = 0 ; }
        for ( x = -48 + *ss ; isdigit ( * ++ ss ) ; ( x *= 10 ) += *ss - 48 ) ; ++ ss ;
        return opt ? x : -x ;
    }
# undef In_Len
}
using namespace In;
namespace Out  
{
     
	# define Out_Len 2000000
    static std :: streambuf* fb ( std :: cout.rdbuf ( ) ) ;
    static char buf [Out_Len], *ss ( buf ) ;
    inline void write ( register int x )  {
     
        static int T [30], tp ( 0 ) ;
        if ( ! x )  {
       *ss ++ =  48 ; *ss ++ = 10 ; return ;  }
        if ( x < 0 )  {
       *ss ++ = 45 ; x = -x ;  }
        while ( x ) T [++ tp] = x % 10 | 48, x /= 10 ;
        while ( tp )  *ss ++ = T [tp --] ;
        *ss ++ = ' ' ;
    }
    inline void flush ( )  {
       fb -> sputn ( buf, ss - buf ) ;  }
# undef Out_Len
}
using namespace Out; 
const int MOD=998244353;
typedef long long ll;
inline int add(const int& x,const int& y){
     return x+y>=MOD? x+y-MOD:x+y;}
inline int dec(const int& x,const int& y){
     return x<y? x-y+MOD:x-y;}
inline int qpow(int a,int p)
{
     
	int ans=1;
	while (p)
	{
     
		if (p&1) ans=(ll)ans*a%MOD;
		a=(ll)a*a%MOD;p>>=1;
	}
	return ans;
}
#define inv(x) qpow(x,MOD-2)
int rt[2][24];
int l,lim,r[MAXN];
inline void init(){
     lim=1<<l;for (int i=0;i<lim;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));}
inline void NTT(int* a,int type)
{
     
	for (int i=0;i<lim;i++) if (i<r[i]) swap(a[i],a[r[i]]);
	for (int L=0;L<l;L++)
	{
     
		int mid=1<<L,len=mid<<1,Wn=rt[type][L+1];
		for (int s=0;s<lim;s+=len)
		{
     
			ll w=1;
			for (int k=0;k<mid;k++,w=w*Wn%MOD)
			{
     
				int x=a[s+k],y=w*a[s+mid+k]%MOD;
				a[s+k]=add(x,y);a[s+mid+k]=dec(x,y);
			}			
		}
	}
	if (type)
	{
     
		int t=inv(lim);
		for (int i=0;i<lim;i++) a[i]=(ll)a[i]*t%MOD;
	}
}
void getinv(int* A,int* B,int n)
{
     
	static int f[MAXN],t[MAXN];
	if (n==1) return (void)(*B=inv(*A));
	getinv(A,t,(n+1)>>1);
	for (l=0;(1<<l)<(n<<1);l++);
	init();
	for (int i=0;i<n;i++) f[i]=A[i];
	for (int i=n;i<lim;i++) f[i]=t[i]=0;
	NTT(f,0);NTT(t,0);
	for (int i=0;i<lim;i++) B[i]=(ll)t[i]*dec(2,(ll)f[i]*t[i]%MOD)%MOD;
	NTT(B,1);
	for (int i=n;i<lim;i++) B[i]=0;
}
void getmod(int* A,int* B,int* R,int n,int m)
{
     
	static int F[MAXN],G[MAXN],H[MAXN],t[MAXN];
	for (int i=0;i<=n;i++) F[n-i]=A[i];
	for (int i=0;i<=m;i++) G[m-i]=B[i];
	getinv(G,t,n-m+1);
	for(l=0;(1<<l)<=(2*n-m);l++);
	init();
	for (int i=n+1;i<lim;i++) F[i]=0;
	for (int i=n-m+1;i<lim;i++) t[i]=0;
	NTT(F,0);NTT(t,0);
	for (int i=0;i<lim;i++) t[i]=(ll)F[i]*t[i]%MOD;
	NTT(t,1);
	for (int i=0;i<=n-m;i++) H[i]=t[n-m-i];
	for (int i=0;i<=m;i++) G[i]=B[i];
	for (l=0;(1<<l)<=n;l++);
	init();
	for (int i=n-m+1;i<lim;i++) H[i]=0;
	for (int i=m+1;i<lim;i++) G[i]=0;
	NTT(H,0);NTT(G,0);
	for (int i=0;i<lim;i++) t[i]=(ll)H[i]*G[i]%MOD;
	NTT(t,1);
	for (int i=0;i<m;i++) R[i]=dec(A[i],t[i]);
}
inline void deriv(int* A,int* B,int n){
     for (int i=0;i<n;i++) B[i]=(ll)A[i+1]*(i+1)%MOD;B[n]=0;}
int x[MAXN],y[MAXN];
#define lc p<<1
#define rc p<<1|1
int* g[MAXN];
void build(int p,int ql,int qr)
{
     
	g[p]=new int[qr-ql+2];
	if (ql==qr) return (void)(g[p][0]=MOD-x[ql],g[p][1]=1);
	int mid=(ql+qr)>>1;
	build(lc,ql,mid);build(rc,mid+1,qr);
	for(l=0;(1<<l)<=qr-ql+1;l++);
	init();
	int L[lim],R[lim];
	memset(L,0,sizeof(L));memset(R,0,sizeof(R));
	for (int i=0;i<=mid-ql+1;i++) L[i]=g[lc][i];
	for (int i=0;i<=qr-mid;i++) R[i]=g[rc][i];
	NTT(L,0);NTT(R,0);
	for (int i=0;i<lim;i++) L[i]=(ll)L[i]*R[i]%MOD;
	NTT(L,1);
	for (int i=0;i<=qr-ql+1;i++) g[p][i]=L[i];
}
int G[MAXN],val[MAXN],ans[MAXN];
void getval(int* F,int p,int ql,int qr,int* ans)//F:0...qr-ql
{
     
	if (qr-ql<=500)
	{
     
		for (int i=ql;i<=qr;i++)
			for (int j=qr-ql;j>=0;j--)
				ans[i]=add((ll)ans[i]*x[i]%MOD,F[j]);
		return;
	}
	int mid=(ql+qr)>>1;
	int G[qr-ql+1];
	getmod(F,g[lc],G,qr-ql,mid-ql+1);
	getval(G,lc,ql,mid,ans);
	getmod(F,g[rc],G,qr-ql,qr-mid);
	getval(G,rc,mid+1,qr,ans);
}
void solve(int p,int ql,int qr,int* ans)
{
     
	if (ql==qr) return (void)(*ans=y[ql]);
	int len,mid=(ql+qr)>>1;
	for(len=0;(1<<len)<=qr-ql;len++);
	int L1[1<<len],R1[1<<len],L2[1<<len],R2[1<<len];
	memset(L1,0,sizeof(L1));memset(R1,0,sizeof(R1));
	memset(L2,0,sizeof(L2));memset(R2,0,sizeof(R2));
	solve(lc,ql,mid,L1);solve(rc,mid+1,qr,R2);
	for (int i=0;i<=mid-ql+1;i++) L2[i]=g[lc][i];
	for (int i=0;i<=qr-mid;i++) R1[i]=g[rc][i];
	l=len;init();
	NTT(L1,0);NTT(R1,0);NTT(L2,0);NTT(R2,0);
	for (int i=0;i<lim;i++) ans[i]=add((ll)L1[i]*R1[i]%MOD,(ll)L2[i]*R2[i]%MOD);
	NTT(ans,1);
}
int main()
{
     
	time_t START=clock();
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	In_init();
	rt[0][23]=qpow(3,119);rt[1][23]=inv(rt[0][23]);
	for (int i=22;i>=0;i--)
	{
     
		rt[0][i]=(ll)rt[0][i+1]*rt[0][i+1]%MOD;
		rt[1][i]=(ll)rt[1][i+1]*rt[1][i+1]%MOD;
	}
	int n=read();
	for (int i=1;i<=n;i++) x[i]=read(),y[i]=read();
	build(1,1,n);
	deriv(g[1],G,n);
	getval(G,1,1,n,val);
	cerr<<(clock()-START)*1000/CLOCKS_PER_SEC;
	for (int i=1;i<=n;i++) y[i]=(ll)y[i]*inv(val[i])%MOD;
	solve(1,1,n,ans);
	for (int i=0;i<n;i++) write(ans[i]);
	flush();
	return 0;
}

Part 3.组合线

阶乘幂

x i ‾ = x ( x + 1 ) ( x + 2 ) . . . ( x + i − 1 ) = ( x + i − 1 ) ! ( x − 1 ) ! x^{\overline{i}}=x(x+1)(x+2)...(x+i-1)=\frac{(x+i-1)!}{(x-1)!} xi=x(x+1)(x+2)...(x+i1)=(x1)!(x+i1)!称为上升阶乘幂,简称上升幂

x i ‾ = x ( x − 1 ) ( x − 2 ) . . . ( x − i + 1 ) = x ! ( x − i ) ! x^{\underline{i}}=x(x-1)(x-2)...(x-i+1)=\frac{x!}{(x-i)!} xi=x(x1)(x2)...(xi+1)=(xi)!x!称为下降阶乘幂,简称下降幂

形如 ∑ i = 0 n a i x i ‾ \sum_{i=0}^na_ix^{\overline{i}} i=0naixi称为上升幂多项式

∑ i = 0 n a i x i ‾ \sum_{i=0}^na_ix^{\underline{i}} i=0naixi称为下降幂多项式

指数生成函数(EGF)

∑ i = 0 ∞ a i i ! x i \sum_{i=0}^{\infin}\frac{a_i}{i!}x^i i=0i!aixi

这样就和组合数那套理论扯上了关系

a i = 1 a_i=1 ai=1的EGF为 e x e^x ex

另外有个奇妙的性质

设两个EGF f ( x ) , g ( x ) f(x),g(x) f(x),g(x)

f ( x ) g ( x ) = ( ∑ i = 0 ∞ f i i ! ) ( ∑ i = 0 ∞ g i i ! ) f(x)g(x)=(\sum_{i=0}^\infin\frac{f_i}{i!})(\sum_{i=0}^\infin\frac{g_i}{i!}) f(x)g(x)=(i=0i!fi)(i=0i!gi)

f ( x ) g ( x ) = ∑ k = 0 ∞ ∑ i = 0 k f i i ! g k − i ( k − i ) ! x k f(x)g(x)=\sum_{k=0}^\infin\sum_{i=0}^k\frac{f_i}{i!}\frac{g_{k-i}}{(k-i)!}x^k f(x)g(x)=k=0i=0ki!fi(ki)!gkixk

= ∑ k = 0 ∞ ( ∑ i = 0 k f i g k − i C k i ) x k k ! =\sum_{k=0}^\infin(\sum_{i=0}^kf_ig_{k-i}C_k^i)\frac{x^k}{k!} =k=0(i=0kfigkiCki)k!xk

也就是说,EGF的乘法相当于系数卷起来再加一个组合数

相当于带了一个标号

下降幂多项式乘法

设下降幂多项式为 f ( x ) f(x) f(x)

F ( x ) F(x) F(x) f ( x ) f(x) f(x)点值指数型生成函数,即

F ( x ) = ∑ i = 0 ∞ f ( i ) i ! x i F(x)=\sum_{i=0}^{\infin}\frac{f(i)}{i!}x^i F(x)=i=0i!f(i)xi

对于一个下降幂单项式 x n ‾ x^{\underline{n}} xn,其点值的 E G F EGF EGF

∑ i = n ∞ i ! ( i − n ) ! i ! x i = ∑ i = n ∞ 1 ( i − n ) ! x i \sum_{i=n}^{\infin}\frac{i!}{(i-n)!i!}x^i=\sum_{i=n}^{\infin}\frac{1}{(i-n)!}x^i i=n(in)!i!i!xi=i=n(in)!1xi

提一个 x n x^n xn出来

∑ i = 0 ∞ 1 i ! x n = e x x n \sum_{i=0}^{\infin}\frac{1}{i!} x^n=e^xx^n i=0i!1xn=exxn

x n x^n xn是它的系数普通型生成函数

什么意思呢?

G ( x ) G(x) G(x) f ( x ) f(x) f(x)系数普通型生成函数

那么有

F ( x ) = e x G ( x ) F(x)=e^xG(x) F(x)=exG(x)

(注意无论哪种生成函数都是普通多项式)

G ( x ) G(x) G(x)是给定的,叉上 e x e^x ex再算上阶乘就可以得到点值, O ( n ) O(n) O(n)乘起来

同时

G ( x ) = e − x F ( x ) G(x)=e^{-x}F(x) G(x)=exF(x)

再乘回来就可以了

第二类斯特林数

定义: S n m S_n^m Snm表示 n n n个不同元素放入 m m m个相同的非空集合的方案数

递推式:

S n m = S n − 1 m − 1 + m S n − 1 m S_n^m=S_{n-1}^{m-1}+mS_{n-1}^m Snm=Sn1m1+mSn1m

即:每来一个新元素

  1. 新开一个集合
  2. 放入已有的 m m m个集合

普及组难度

如何求通项公式?

假设集合可以为空,方案数为 m n m^n mn

现在尝试用第二类斯特林数表示这玩意

我们枚举在哪些集合放了这 n n n个元素,一个组合数就可以了。因为左边可以换顺序,而斯特林数是无序的,所以再乘一个阶乘。

m n = ∑ i = 0 m S n i i ! C m i m^n=\sum_{i=0}^{m}S_n^ii!C_m^i mn=i=0mSnii!Cmi

二项式反演一波

f ( m ) = m n , g ( i ) = S n i i ! f(m)=m^n,g(i)=S_ n^ii! f(m)=mn,g(i)=Snii!

f ( m ) = ∑ i = 0 m C m i g ( i ) f(m)=\sum_{i=0}^mC_m^ig(i) f(m)=i=0mCmig(i)

g ( m ) = ∑ i = 0 m ( − 1 ) m − i C m i f ( i ) g(m)=\sum_{i=0}^m(-1)^{m-i}C_m^if(i) g(m)=i=0m(1)miCmif(i)

S n m m ! = ∑ i = 0 m ( − 1 ) m − i C m i i n S_ n^mm!=\sum_{i=0}^m(-1)^{m-i}C_m^ii^n Snmm!=i=0m(1)miCmiin

S n m = 1 m ! ∑ i = 0 m ( − 1 ) m − i C m i i n S_ n^m=\frac{1}{m!}\sum_{i=0}^m(-1)^{m-i}C_m^ii^n Snm=m!1i=0m(1)miCmiin

得到了通项公式,但好像没啥用

我们把组合数拆开

S n m = 1 m ! ∑ i = 0 m ( − 1 ) m − i m ! i ! ( m − i ) ! i n S_ n^m=\frac{1}{m!}\sum_{i=0}^m(-1)^{m-i}\frac{m!}{i!(m-i)!}i^n Snm=m!1i=0m(1)mii!(mi)!m!in

发现可以约掉

S n m = ∑ i = 0 m ( − 1 ) m − i 1 i ! ( m − i ) ! i n S_ n^m=\sum_{i=0}^m(-1)^{m-i}\frac{1}{i!(m-i)!}i^n Snm=i=0m(1)mii!(mi)!1in

S n m = ∑ i = 0 m i n i ! ( − 1 ) m − i ( m − i ) ! S_ n^m=\sum_{i=0}^m\frac{i^n}{i!}\frac{(-1)^{m-i}}{(m-i)!} Snm=i=0mi!in(mi)!(1)mi

我们发现这是个卷积形式

所以可以NTT求出第二类斯特林数的某一行(对于 S n m S_n^m Snm, n n n是行数, m m m是列数)


对第二类斯特林数的每一列构造生成函数 S m ( x ) S_m(x) Sm(x)

由递推式

S n m = S n − 1 m − 1 + m S n − 1 m S_n^m=S_{n-1}^{m-1}+mS_{n-1}^m Snm=Sn1m1+mSn1m

即:上一个右移再加上自己右移乘以一个系数

S m ( x ) = x S m − 1 ( x ) + m x S m ( x ) S_m(x)=xS_{m-1}(x)+mxS_m(x) Sm(x)=xSm1(x)+mxSm(x)

S m ( x ) = x S m − 1 ( x ) 1 − m x S_m(x)=\frac{xS_{m-1}(x)}{1-mx} Sm(x)=1mxxSm1(x)

迭代下去

S m ( x ) = x m ∏ i = 1 m ( 1 − i x ) S_m(x)=\frac{x^m}{\prod_{i=1}^m(1-ix)} Sm(x)=i=1m(1ix)xm

下面用分治NTT算出来求个逆乘上上面,就可以得到一列

第一类斯特林数

定义: s n m s_n^m snm表示 n n n个元素分成 m m m个非空圆排列(即:旋转后相同视为相同)的方案数。

代表人物: 0 , 6 , 11 , 6 0,6,11,6 0,6,11,6

递推式:

s n m = s n − 1 m − 1 + ( n − 1 ) s n − 1 m s_n^m=s_{n-1}^{m-1}+(n-1)s_{n-1}^m snm=sn1m1+(n1)sn1m

即每新来一个元素

  1. 新开一个圆排列
  2. 放在一个已有的元素之后

仍然是普及组难度

快速求法在后面讲

一些微妙的性质

x n = ∑ i = 0 n S n i x i ‾ x^n=\sum_{i=0}^nS_n^ix^{\underline{i}} xn=i=0nSnixi

证明可以考虑归纳法

x n = x ⋅ x n − 1 = x ∑ i = 0 n − 1 S n − 1 i x i ‾ = ∑ i = 0 n − 1 S n − 1 i x ⋅ x i ‾ x^n=x·x^{n-1}=x\sum_{i=0}^{n-1}S_{n-1}^ix^{\underline i}=\sum_{i=0}^{n-1}S_{n-1}^ix·x^{\underline i} xn=xxn1=xi=0n1Sn1ixi=i=0n1Sn1ixxi

冷静分析,

x ⋅ x i ‾ = x ⋅ x ( x − 1 ) ( x − 2 ) . . . ( x − i + 1 ) x·x^{\underline i}=x·x(x-1)(x-2)...(x-i+1) xxi=xx(x1)(x2)...(xi+1)

显然

x i + 1 ‾ = x ( x − 1 ) ( x − 2 ) . . . ( x − i + 1 ) ( x − i ) x^{\underline{i+1}}=x(x-1)(x-2)...(x-i+1)(x-i) xi+1=x(x1)(x2)...(xi+1)(xi)

两式相减,得

x ⋅ x i ‾ = x i + 1 ‾ + i ⋅ x i ‾ x·x^{\underline i}=x^{\underline{i+1}}+i·x^{\underline i} xxi=xi+1+ixi

代回去

x n = ∑ i = 0 n − 1 S n − 1 i ( x i + 1 ‾ + i ⋅ x i ‾ ) x^n=\sum_{i=0}^{n-1}S_{n-1}^i(x^{\underline{i+1}}+i·x^{\underline i}) xn=i=0n1Sn1i(xi+1+ixi)

拆开

= ∑ i = 0 n − 1 S n − 1 i x i + 1 ‾ + ∑ i = 0 n − 1 i S n − 1 i x i ‾ =\sum_{i=0}^{n-1}S_{n-1}^ix^{\underline{i+1}}+\sum_{i=0}^{n-1}iS_{n-1}^ix^{\underline i} =i=0n1Sn1ixi+1+i=0n1iSn1ixi

考虑把 x i + 1 ‾ 变 成 x i ‾ x^{\underline{i+1}}变成x^{\underline i} xi+1xi

改下求和项就可以了

= ∑ i = 1 n S n − 1 i − 1 x i ‾ + ∑ i = 0 n − 1 i S n − 1 i x i ‾ =\sum_{i=1}^nS_{n-1}^{i-1}x^{\underline i}+\sum_{i=0}^{n-1}iS_{n-1}^ix^{\underline i} =i=1nSn1i1xi+i=0n1iSn1ixi

右边的 0 0 0不要了,上面把 n n n加上,这样可以写到一起

= ∑ i = 1 n S n − 1 i − 1 x i ‾ + ∑ i = 1 n i S n − 1 i x i ‾ =\sum_{i=1}^nS_{n-1}^{i-1}x^{\underline i}+\sum_{i=1}^niS_{n-1}^ix^{\underline i} =i=1nSn1i1xi+i=1niSn1ixi

合并起来

= ∑ i = 1 n ( S n − 1 i − 1 + i S n − 1 i ) x i ‾ =\sum_{i=1}^n(S_{n-1}^{i-1}+iS_{n-1}^i)x^{\underline i} =i=1n(Sn1i1+iSn1i)xi

发现了什么不得了的事情

= ∑ i = 1 n S n i x i ‾ =\sum_{i=1}^nS_n^ix^{\underline i} =i=1nSnixi

类似的,我们可以得到

x n ‾ = ∑ i = 0 n s n i x i x^{\overline n}=\sum_{i=0}^ns_n^ix^i xn=i=0nsnixi

根据这个可以求出第一类斯特林数的一行

显然有个 O ( n log ⁡ n 2 ) O(n\log_n^2) O(nlogn2)的分治NTT做法

倍增可以做到 O ( n log ⁡ n ) O(n\log n) O(nlogn)

考虑现在求 x 2 n ‾ x^{\underline{2n}} x2n

递归求出

f ( x ) = x n ‾ = ∑ i = 0 n a i x i f(x)=x^{\underline n}=\sum_{i=0}^na_ix^i f(x)=xn=i=0naixi

显然 x 2 n ‾ = x n ‾ ( x + n ) n ‾ x^{\underline{2n}}=x^{\underline n}(x+n)^{\underline n} x2n=xn(x+n)n,所以我们只需要求出 f ( x + n ) f(x+n) f(x+n)

f ( x + n ) = ∑ i = 0 n a i ( x + n ) i f(x+n)=\sum_{i=0}^na_i(x+n)^i f(x+n)=i=0nai(x+n)i

二项式定理拆开

f ( x + n ) = ∑ i = 0 n a i ∑ j = 0 i C i j x j n i − j f(x+n)=\sum_{i=0}^na_i\sum_{j=0}^iC_i^jx^jn^{i-j} f(x+n)=i=0naij=0iCijxjnij

换个顺序

f ( x + n ) = ∑ j = 0 n x j ∑ i = j n a i C i j n i − j f(x+n)=\sum_{j=0}^nx^j\sum_{i=j}^ na_iC_i^jn^{i-j} f(x+n)=j=0nxji=jnaiCijnij

把组合数也拆开

f ( x + n ) = ∑ j = 0 n x j ∑ i = j n a i i ! j ! ( i − j ) ! n i − j f(x+n)=\sum_{j=0}^nx^j\sum_{i=j}^ na_i\frac{i!}{j!(i-j)!}n^{i-j} f(x+n)=j=0nxji=jnaij!(ij)!i!nij

f ( x + n ) = ∑ j = 0 n x j j ! ∑ i = j n a i i ! n i − j ( i − j ) ! f(x+n)=\sum_{j=0}^n\frac{x^j}{j!}\sum_{i=j}^ na_ii!\frac{n^{i-j}}{(i-j)!} f(x+n)=j=0nj!xji=jnaii!(ij)!nij

a a a翻一下

f ( x + n ) = ∑ j = 0 n x j j ! ∑ i = j n a n − i ( n − i ) ! n i − j ( i − j ) ! f(x+n)=\sum_{j=0}^n\frac{x^j}{j!}\sum_{i=j}^ na_{n-i}(n-i)!\frac{n^{i-j}}{(i-j)!} f(x+n)=j=0nj!xji=jnani(ni)!(ij)!nij

发现右边是个卷积 设卷出来是 b b b

f ( x + n ) = ∑ j = 0 n x j j ! b n − j f(x+n)=\sum_{j=0}^n\frac{x^j}{j!}b_{n-j} f(x+n)=j=0nj!xjbnj

再把 b b b翻一下

f ( x + n ) = ∑ j = 0 n x j j ! b j f(x+n)=\sum_{j=0}^n\frac{x^j}{j!}b_j f(x+n)=j=0nj!xjbj

就可以求出来

当然如果 n n n为奇数就手动乘上 ( x + n − 1 ) (x+n-1) (x+n1)


如何求一列?

我们发现一列的意义是 m m m个圆排列凑出若干点的方案,考虑生成函数

因为有标号,考虑构造EGF

首先如果是一个圆排列,方案数是 ( n − 1 ) ! (n-1)! (n1)!

其EGF为

f ( x ) = ∑ i = 1 ∞ ( n − 1 ) ! n ! x i f(x)=\sum_{i=1}^\infin\frac{(n-1)!}{n!}x^i f(x)=i=1n!(n1)!xi

= ∑ i = 1 ∞ 1 i x i =\sum_{i=1}^\infin\frac{1}{i}x^i =i=1i1xi

m m m个圆排列就是 m m m f ( x ) f(x) f(x)乘起来,因为圆排列之间没有顺序,所以除以一个阶乘

f m ( x ) m ! \frac{f^m(x)}{m!} m!fm(x)

f ( x ) f(x) f(x)没有常数项,所以需要平移一位

下降幂多项式转普通多项式

因为上面已经证明过下降幂多项式的点值的 EGF 等于它的系数的 OGF 叉上 e x e^x ex

所以算出点值跑快速插值就可以了。

点值连续,不用写多点求值,还是比较清真的。

#include 
#include 
#include 
#include 
#define MAXN 524288
using namespace std;
const int MOD=998244353;
typedef long long ll;
inline int read()
{
     
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline int add(const int& x,const int& y){
     return x+y>=MOD? x+y-MOD:x+y;}
inline int dec(const int& x,const int& y){
     return x<y? x-y+MOD:x-y;}
inline int qpow(int a,int p)
{
     
	int ans=1;
	while (p)
	{
     
		if (p&1) ans=(ll)ans*a%MOD;
		a=(ll)a*a%MOD;p>>=1;
	}
	return ans;
}
#define inv(x) qpow(x,MOD-2)
int rt[2][24],fac[MAXN],finv[MAXN];
int l,r[MAXN],lim;
inline void init(){
     lim=1<<l;for (int i=0;i<lim;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));}
inline void NTT(int* a,int type)
{
     
	for (int i=0;i<lim;i++) if (i<r[i]) swap(a[i],a[r[i]]);
	for (int L=0;L<l;L++)
	{
     
		int mid=1<<L,len=mid<<1;
		ll Wn=rt[type][L+1];
		for (int s=0;s<lim;s+=len)
			for (int k=0,w=1;k<mid;k++,w=w*Wn%MOD)
			{
     
				int x=a[s+k],y=(ll)w*a[s+mid+k]%MOD;
				a[s+k]=add(x,y),a[s+mid+k]=dec(x,y);
			}
	}
	if (type)
	{
     
		int t=inv(lim);
		for (int i=0;i<lim;i++) a[i]=(ll)a[i]*t%MOD;
	}
}
int F[MAXN],G[MAXN];
int* g[MAXN<<1];
#define lc p<<1
#define rc p<<1|1
void build(int p,int ql,int qr)
{
     
	g[p]=new int[qr-ql+2];
	if (ql==qr) return (void)(g[p][0]=MOD-ql,g[p][1]=1);
	int mid=(ql+qr)>>1;
	build(lc,ql,mid);build(rc,mid+1,qr);
	for (l=0;(1<<l)<=(qr-ql+1);l++);
	init();
	int L[lim],R[lim];
	memset(L,0,sizeof(L));memset(R,0,sizeof(R));
	for (int i=0;i<=mid-ql+1;i++) L[i]=g[lc][i];
	for (int i=0;i<=qr-mid;i++) R[i]=g[rc][i];
	NTT(L,0);NTT(R,0);
	for (int i=0;i<lim;i++) L[i]=(ll)L[i]*R[i]%MOD;
	NTT(L,1);
	for (int i=0;i<=qr-ql+1;i++) g[p][i]=L[i];
}
void solve(int p,int* y,int* ans,int ql,int qr)
{
     
	if (ql==qr) return (void)(*ans=y[ql]);
	int mid=(ql+qr)>>1;
	for (l=0;(1<<l)<=(qr-ql);l++);
	lim=1<<l;
	int len=l;
	int fl[lim],fr[lim],gl[lim],gr[lim];
	memset(fl,0,sizeof(fl));memset(fr,0,sizeof(fr));
	memset(gl,0,sizeof(gl));

你可能感兴趣的:(多项式全家桶学习笔记【持续更新】)