模板:基于NTT的多项式操作

基于NTT的多项式操作

因为实在是太多东西啦,所以就全部整理了以下,持续更新ing~
前置知识:NTT
顺便说一句,代码采用重载vector封装的形式(因为懒得自己开结构体)
下面是几个已经封装的基础代码,为了方便浏览,先贴出来:

typedef std::vector<int> VI;
int fix(int x) {return (x >> 31 & P) + x;}
int Pow(int x, int k) {
    int r = 1;
    for(;k; k >>= 1, x = 1LL * x * x % P)
        if(k & 1)
            r = 1LL * r * x % P;
    return r;
}
int Inv(int x) {return Pow(x, P - 2);}
void Pre(int m) {
    int x = 0; L = 1;
    for(;(L <<= 1) < m; ++x) ;
    for(int i = 1;i < L; ++i)
        R[i] = R[i >> 1] >> 1 | (i & 1) << x;
    int wn = Pow(3, (P - 1) / L); w[0] = 1;
    for(int i = 1;i < L; ++i)
        w[i] = 1LL * w[i - 1] * wn % P;
    InvL = Inv(L);
}
void NTT(int *F) {
    for(int i = 0;i < L; ++i)
        if(R[i] > i)
            std::swap(F[i], F[R[i]]);
    for(int i = 1, d = L >> 1; i < L; i <<= 1, d >>= 1)
        for(int j = 0;j < L; j += i << 1) {
            int *l = F + j, *r = F + i + j, *p = w, tp;
            for(int k = i; k--; ++l, ++r, p += d)
                tp = 1LL * *p * *r % P, *r = (*l - tp) % P, *l = (*l + tp) % P;
        }
}
void Fill(const VI &a, int *A, int m) {
    m = std::min(m, (int)a.size());
    for(int i = 0;i < m; ++i)
        A[i] = a[i];
    for(int i = m; i < L; ++i)
        A[i] = 0;
}
void Fill(int *A, int *B, int m) {
    for(int i = 0;i < m; ++i)
        B[i] = A[i];
    for(int i = m; i < L; ++i)
        B[i] = 0;
}
VI operator * (const VI &a, const VI &b) {
	const int Lim = 3000;
    int m = a.size() + b.size() - 1;
    static VI c; static int A[N], B[N];
    c.resize(m, 0);
    if(a.size() * b.size() <= Lim) { //小范围暴力
		for(int i = 0;i < m; ++i) 
			c[i] = 0;
    	for(int i = 0;i < a.size(); ++i)
    		for(int j = 0;j < b.size(); ++j)
    			c[i + j] = (c[i + j] + 1LL * a[i] * b[j]) % P;
    	return c;
	}
	Pre(m);
    Fill(a, A, a.size());
    Fill(b, B, b.size());
    NTT(A); NTT(B);
    for(int i = 0;i < L; ++i)
        A[i] = 1LL * A[i] * B[i] % P;
    NTT(A);
    for(int i = 0;i < m; ++i)
        c[i] = fix(1LL * A[L - i & L - 1] * InvL % P);
    return c;
}
VI operator - (const VI &a, const VI &b) {
    int n = std::max(a.size(), b.size());
    static VI c; c.resize(n, 0);
    for(int i = 0;i < a.size(); ++i)
        c[i] = a[i];
    for(int i = 0;i < b.size(); ++i)
        c[i] = fix(c[i] - b[i]);
    return c;
}
VI operator + (const VI &a, const VI &b) {
    int n = std::max(a.size(), b.size());
    static VI c; c.resize(n, 0);
    for(int i = 0;i < a.size(); ++i)
        c[i] = a[i];
    for(int i = 0;i < b.size(); ++i)
    	c[i] = (c[i] + b[i]) % P;
    return c;
}

迭代相关:多项式求逆,开根,求exp

这些我们熟悉的多项式操作都是采用迭代法解决,既然都是迭代法,必有其共性,因此下面便脱离传统的证明方法,采用牛顿迭代法来进行一波推导

知识点:泰勒展开

下面首先介绍一下泰勒展开:
对于一个函数 f ( x ) f(x) f(x),其在 x = x 0 x=x_0 x=x0处的泰勒公式如下:
f ( x ) = f ( x 0 ) 0 ! + f ′ ( x 0 ) 1 ! ( x − x 0 ) + f ′ ′ ( x 0 ) 2 ! ( x − x 0 ) 2 ⋯ f ( n ) ( x 0 ) n ! ( x − x 0 ) n f(x)=\frac{f(x_0)}{0!}+\frac{f&#x27;(x_0)}{1!}(x-x_0)+\frac{f&#x27;&#x27;(x_0)}{2!}(x-x_0)^2\cdots\frac{f^{(n)}(x_0)}{n!}(x-x_0)^n f(x)=0!f(x0)+1!f(x0)(xx0)+2!f(x0)(xx0)2n!f(n)(x0)(xx0)n

知识点:牛顿迭代

这是一种逼近零点的科技。(高中数学课本上有,文化课选手应该了解)
对于一个函数 f ( x ) f(x) f(x),我们考虑以 x i x_i xi作为起点逼近其零点。
方法就是,作 ( x i , y i ) (x_i,y_i) (xi,yi)处的切线,设其交 x x x轴于 x i + 1 x_{i+1} xi+1,以 x i + 1 x_{i+1} xi+1作为下一次的起点重复上述过程不断逼近零点。
推一推式子:
y − f ( x i ) = f ′ ( x i ) ( x − x i ) y-f(x_i)=f&#x27;(x_i)(x-x_i) yf(xi)=f(xi)(xxi)
y = 0 y=0 y=0,得到 x i + 1 = x i − f ( x i ) f ′ ( x i ) x_{i+1}=x_i-\frac{f(x_i)}{f&#x27;(x_i)} xi+1=xif(xi)f(xi)
有没有发现式子其实挺熟悉的?
考虑直线方程: y = f ( x i ) + f ′ ( x i ) ( x − x i ) y=f(x_i)+f&#x27;(x_i)(x-x_i) y=f(xi)+f(xi)(xxi)
发现直线方程就是 f ( x ) f(x) f(x) x i x_i xi处的一阶泰勒展开。
形式化地,我们可以这样描述牛顿迭代法:
用函数 f ( x ) f(x) f(x) f ( x i ) f(x_i) f(xi)处的一阶展开方程的零点作为下一次迭代的起点 x i x_i xi,这样的迭代法就是牛顿迭代法。
这个东西对于多项式有什么好处呢?
我们不难发现定义一个以多项式为自变量的函数 G ( x ) G(x) G(x),泰勒公式仍然成立。
我们首先将题目的形式转化为求 F ( x ) ∣ G ( F ( x ) ) ≡ 0 ( m o d &ThinSpace;&ThinSpace; x n ) F(x)|G(F(x))\equiv 0(\mod x^n) F(x)G(F(x))0(modxn)
迭代法的一般考虑步骤是:
已知 G ( F 0 ( x ) ) ≡ 0 ( m o d &ThinSpace;&ThinSpace; x n ) G(F_0(x))\equiv 0(\mod x^n) G(F0(x))0(modxn)
G ( F ( x ) ) ≡ 0 ( m o d &ThinSpace;&ThinSpace; x 2 n ) G(F(x))\equiv 0 (\mod x^{2n}) G(F(x))0(modx2n)
考虑 G ( x ) G(x) G(x) F 0 ( x ) F_0(x) F0(x)处的泰勒展开逼近 F ( x ) F(x) F(x)
G ( F ( x ) ) ≡ G ( F 0 ( x ) ) + G ′ ( F 0 ( x ) ) ( F ( x ) − F 0 ( x ) ) + G ′ ′ ( F 0 ( x ) ) ( F ( x ) − F 0 ( x ) ) 2 ⋯ ( m o d &ThinSpace;&ThinSpace; x n ) G(F(x))\equiv G(F_0(x))+G&#x27;(F_0(x))(F(x)-F_0(x))+G&#x27;&#x27;(F_0(x))(F(x)-F_0(x))^2\cdots(\mod x^n) G(F(x))G(F0(x))+G(F0(x))(F(x)F0(x))+G(F0(x))(F(x)F0(x))2(modxn)
事实上,我们有 F ( x ) ≡ F 0 ( x ) ( m o d &ThinSpace;&ThinSpace; x n ) F(x)\equiv F_0(x) (\mod x^n) F(x)F0(x)(modxn)
那么 F ( x ) − F 0 ( x ) ≡ 0 ( m o d &ThinSpace;&ThinSpace; x n ) ⇒ ( F ( x ) − F 0 ( x ) ) 2 ≡ 0 ( m o d &ThinSpace;&ThinSpace; x 2 n ) F(x)-F_0(x)\equiv 0 (\mod x^n)\Rightarrow (F(x)-F_0(x))^2\equiv 0 (\mod x^{2n}) F(x)F0(x)0(modxn)(F(x)F0(x))20(modx2n)
这一步推导是因为 F ( x ) − F 0 ( x ) F(x)-F_0(x) F(x)F0(x)的最低次幂是 x n x^n xn,平方之后最低次幂就变成了 x 2 n x^{2n} x2n
于是我们有 G ( F ( x ) ) ≡ G ( F 0 ( x ) ) + G ′ ( F 0 ( x ) ) ( F ( x ) − F 0 ( x ) ) ( m o d &ThinSpace;&ThinSpace; x 2 n ) G(F(x))\equiv G(F_0(x))+G&#x27;(F_0(x))(F(x)-F_0(x)) (\mod x^{2n}) G(F(x))G(F0(x))+G(F0(x))(F(x)F0(x))(modx2n)
也就是说, F ( x ) F(x) F(x) F 0 ( x ) F_0(x) F0(x)牛顿迭代的结果。
根据牛顿迭代的结果,我们可以直接得到:
F ( x ) = F 0 ( x ) − G ( F 0 ( x ) ) G ′ ( F 0 ( x ) ) F(x)=F_0(x)-\frac{G(F_0(x))}{G&#x27;(F_0(x))} F(x)=F0(x)G(F0(x))G(F0(x))
基于这个结论,我们可以采用类似倍增的方法来求得最终需要的 F ( x ) ) F(x)) F(x))

多项式求逆

已知 F ( x ) F(x) F(x),求 R ( x ) ∣ F ( x ) R ( x ) ≡ 1 ( m o d &ThinSpace;&ThinSpace; x n ) R(x)|F(x)R(x)\equiv 1(\mod x^n) R(x)F(x)R(x)1(modxn)
原式等价于
F ( x ) − R − 1 ( x ) ≡ 0 ( m o d &ThinSpace;&ThinSpace; x n ) F(x)-R^{-1}(x)\equiv 0(\mod x^n) F(x)R1(x)0(modxn)
注意构造的时候一般让 F ( x ) F(x) F(x)在常数位置上,这样求导的时候直接消掉
构造函数 G ( R ( x ) ) = F ( x ) − R − 1 ( x ) G(R(x))=F(x)-R^{-1}(x) G(R(x))=F(x)R1(x)
求导 G ′ ( R ( x ) ) = R − 2 ( x ) G&#x27;(R(x))=R^{-2}(x) G(R(x))=R2(x)
假设求得 G ( R 0 ( x ) ) ≡ 0 ( m o d &ThinSpace;&ThinSpace; x n ) G(R_0(x))\equiv 0(\mod x^n) G(R0(x))0(modxn)
由刚才的结论 R ( x ) ≡ R 0 ( x ) − G ( R 0 ( x ) ) G ′ ( R 0 ( x ) ) ≡ R 0 ( x ) − F ( x ) − R 0 − 1 ( x ) R 0 − 2 ( x 0 ) ≡ 2 R 0 ( x ) − F ( x ) R 0 2 ( x ) ( m o d &ThinSpace;&ThinSpace; x 2 n ) R(x)\equiv R_0(x)-\frac{G(R_0(x))}{G&#x27;(R_0(x))}\equiv R_0(x)-\frac{F(x)-R_0^{-1}(x)}{R_0^{-2}(x_0)}\equiv 2R_0(x)-F(x)R_0^2(x) (\mod x^{2n}) R(x)R0(x)G(R0(x))G(R0(x))R0(x)R02(x0)F(x)R01(x)2R0(x)F(x)R02(x)(modx2n)
初值直接费马小定理。
结合多项式乘法,结束了!
复杂度由主定理得到 T ( n ) = T ( n 2 ) + O ( n l o g n ) = O ( n l o g n ) T(n)=T(\frac{n}{2})+O(nlogn)=O(nlogn) T(n)=T(2n)+O(nlogn)=O(nlogn)

VI Inv(const VI &a, const int m) { //多项式求逆
    static int A[N], B[N], C[N];
    for(int i = 0;i < m; ++i)
        A[i] = 0;
    A[0] = Inv(a[0]);
    int n = 1;
    for(;n < m;) {
        Pre(n << 2);
        Fill(a, B, n << 1);
        Fill(A, C, n);
        NTT(B); NTT(C);
        for(int i = 0;i < L; ++i)	
            B[i] = 1LL * B[i] * C[i] % P * C[i] % P;
        NTT(B);
        n <<= 1;
        for(int i = 0;i < n; ++i)
            A[i] = ((A[i] << 1) - 1LL * B[L - i & L - 1] * InvL) % P;
    }
    static VI c; c.resize(m);
    for(int i = 0;i < m; ++i)	
        c[i] = fix(A[i]);
    return c;
}
多项式开根

已知 F ( x ) F(x) F(x),求 B ( x ) ∣ B ( x ) ≡ F ( x ) ( m o d &ThinSpace;&ThinSpace; x n ) B(x)|B(x)\equiv \sqrt {F(x)}(\mod x^n) B(x)B(x)F(x) (modxn)
同样地让 F ( x ) F(x) F(x)在常数位上
B 2 ( x ) − F ( x ) ≡ 0 ( m o d &ThinSpace;&ThinSpace; x n ) B^2(x)-F(x)\equiv 0(\mod x^n) B2(x)F(x)0(modxn)
构造函数 G ( B ( x ) ) = B 2 ( x ) − F ( x ) G(B(x))=B^2(x)-F(x) G(B(x))=B2(x)F(x)
求导 G ′ ( B ( x ) ) = 2 B ( x ) G&#x27;(B(x))=2B(x) G(B(x))=2B(x)
迭代 B ( x ) = B 0 ( x ) − G ( B 0 ( x ) ) G ′ ( B 0 ( x ) ) = B 0 ( x ) − B 0 2 ( x ) − F ( x ) 2 B 0 ( x ) = F ( x ) + B 0 2 ( x ) 2 B 0 ( x ) B(x)=B_0(x)-\frac{G(B_0(x))}{G&#x27;(B_0(x))}=B_0(x)-\frac{B_0^2(x)-F(x)}{2B_0(x)}=\frac{F(x)+B_0^2(x)}{2B_0(x)} B(x)=B0(x)G(B0(x))G(B0(x))=B0(x)2B0(x)B02(x)F(x)=2B0(x)F(x)+B02(x)
初值一般为1,否则的话要采用二次剩余或者原根BSGS
结合多项式求逆,多项式乘法可以解决,复杂度同上。

VI Sqrt(const VI &a) { //多项式开根 
    static VI b, c;
    b.resize(1, 1); //仅仅在a0=1的时候成立,否则的话要用BSGS 
    int n = 1, m = a.size();
    for(;n < m;) {
        n <<= 1;
        c = Inv(b, n);
        b = b * b; b.resize(n);
        for(int i = 0;i < std::min(n, m); ++i)
            b[i] = 1LL * (b[i] + a[i]) * inv2 % P;
        b = b * c;
        b.resize(n);
    }
    b.resize(m);
    return b;
}
多项式求exp
前置知识:多项式求ln

这个东西简单很多
A ( x ) ≡ L n ( F ( x ) ) ( m o d &ThinSpace;&ThinSpace; x n ) A(x)\equiv Ln(F(x))(\mod x^n) A(x)Ln(F(x))(modxn)
两边同时 x x x求导(注意和之前对多项式求导不一样!)
A ′ ( x ) ≡ F ′ ( x ) F ( x ) A&#x27;(x)\equiv \frac{F&#x27;(x)}{F(x)} A(x)F(x)F(x)
结合求逆再积分回去即可,复杂度同。
求导积分就不多说了吧!

VI deri(const VI &a) { //多项式求导 
    static VI c; 
    if(a.size() == 1) return VI(1, 0);
    c.resize(a.size() - 1);
    for(int i = 0;i < a.size() - 1; ++i)
        c[i] = 1LL * a[i + 1] * (i + 1) % P;
    return c;
}
VI inter(const VI &a) { //多项式积分 
    static VI c; c.resize(a.size() + 1);
    c[0] = 0;
    for(int i = 0;i < a.size(); ++i)
        c[i + 1] = 1LL * a[i] * Inv(i + 1) % P;
    return c;
}
VI Ln(const VI &a, const int m) { //多项式求Ln 
    static VI c;
    c = deri(a) * Inv(a, m - 1);
    c.resize(m - 1);
    return inter(c);
}

接下来是求exp
已知 F ( x ) F(x) F(x),求 A ( x ) ∣ A ( x ) ≡ e F ( x ) ( m o d &ThinSpace;&ThinSpace; x n ) A(x)|A(x)\equiv e^ {F(x)}(\mod x^n) A(x)A(x)eF(x)(modxn)
F ( x ) − l n A ( x ) ≡ ( m o d &ThinSpace;&ThinSpace; x n ) F(x) - lnA(x) \equiv(\mod x^n) F(x)lnA(x)(modxn)
构造函数 G ( A ( x ) ) = F ( x ) − l n ( A ( x ) ) G(A(x))=F(x)-ln(A(x)) G(A(x))=F(x)ln(A(x))
求导 G ′ ( A ( x ) ) = − A − 1 ( x ) G&#x27;(A(x))=-A^{-1}(x) G(A(x))=A1(x)
迭代 A ( x ) = A 0 ( x ) − G ( A 0 ( x ) ) G ′ ( A 0 ( x ) ) = A 0 ( x ) + F ( x ) − l n ( A ( x ) ) A 0 − 1 ( x ) = A 0 ( x ) ( 1 + F ( x ) − l n ( A ( x ) ) ) A(x)=A_0(x)-\frac{G(A_0(x))}{G&#x27;(A_0(x))}=A_0(x)+\frac{F(x)-ln(A(x))}{A_0^{-1}(x)}=A_0(x)(1+F(x)-ln(A(x))) A(x)=A0(x)G(A0(x))G(A0(x))=A0(x)+A01(x)F(x)ln(A(x))=A0(x)(1+F(x)ln(A(x)))
初值一般为1,否则无意义。
结合多项式求逆,多项式求Ln,多项式乘法可以解决,复杂度同上。

VI Exp(const VI &a, const int m) { //多项式求exp 
	static VI f, g; f.resize(1, 1);
	int n = 1, sz = a.size();
	for(;n < m;) {
		n <<= 1; g = Ln(f, n); 
		for(int i = 0;i < n; ++i)
			g[i] = i < sz ? fix(a[i] - g[i]) : fix(-g[i]);
		(++g[0]) %= P;
		f = f * g; f.resize(n);
	}
	return f;
}

各种黑科技

多项式快速幂

给定 k , A ( x ) k,A(x) k,A(x),求 B ( x ) ≡ A ( x ) k ( m o d &ThinSpace;&ThinSpace; x n ) B(x)\equiv A(x)^k(\mod x^n) B(x)A(x)k(modxn)
k ≤ 1 0 1 0 5 k \le 10^{10^5} k10105
B ( x ) ≡ e k l n ( A ( x ) ) ( m o d &ThinSpace;&ThinSpace; x n ) B(x)\equiv e^{kln(A(x))}(\mod x^n) B(x)ekln(A(x))(modxn)

VI Pow(const VI &a, int k, int n) { //多项式快速幂 
    static VI f; f = Ln(a, n);
    for(int i = 0;i < n; ++i)
        f[i] = 1LL * f[i] * k % P;
    return Exp(f, n);
}
多项式除法

给定 F ( x ) , G ( x ) F(x),G(x) F(x),G(x),阶数分别为 n , m n,m n,m,求 F ( x ) = Q ( x ) G ( x ) + R ( x ) F(x)=Q(x)G(x)+R(x) F(x)=Q(x)G(x)+R(x),其中 R ( x ) R(x) R(x)的阶数小于 m m m
F ( x ) = Q ( x ) G ( x ) + R ( x ) ⇒ F ( 1 x ) = Q ( 1 x ) G ( 1 x ) + R ( 1 x ) F(x)=Q(x)G(x)+R(x)\Rightarrow F(\frac{1}{x})=Q(\frac{1}{x})G(\frac{1}{x})+R(\frac{1}{x}) F(x)=Q(x)G(x)+R(x)F(x1)=Q(x1)G(x1)+R(x1)
⇒ x n F ( 1 x ) = x n − m Q ( 1 x ) x m G ( 1 x ) + x n R ( 1 x ) ⇒ F R ( x ) = Q R ( x ) G R ( x ) + x n − m + 1 R R ( x ) \Rightarrow x^nF(\frac{1}{x})=x^{n-m}Q(\frac{1}{x})x^mG(\frac{1}{x})+x^nR(\frac{1}{x})\Rightarrow F_R(x)=Q_R(x)G_R(x)+x^{n-m+1}R_R(x) xnF(x1)=xnmQ(x1)xmG(x1)+xnR(x1)FR(x)=QR(x)GR(x)+xnm+1RR(x)
其中 F R ( x ) F_R(x) FR(x)表示系数反转后的多项式。
显然 Q ( x ) Q(x) Q(x)的阶数为 n − m n-m nm,于是
F R ( x ) ≡ Q R ( x ) G R ( x ) ( m o d &ThinSpace;&ThinSpace; x n − m + 1 ) F_R(x)\equiv Q_R(x)G_R(x) (\mod x^{n-m+1}) FR(x)QR(x)GR(x)(modxnm+1)
⇒ Q R ( x ) ≡ F R ( x ) G R − 1 ( m o d &ThinSpace;&ThinSpace; x n − m + 1 ) \Rightarrow Q_R(x)\equiv F_R(x)G_R^{-1} (\mod x^{n-m+1}) QR(x)FR(x)GR1(modxnm+1)
结合多项式求逆,即可求得 Q ( x ) Q(x) Q(x),然后有 R ( x ) = F ( x ) − Q ( x ) G ( x ) R(x)=F(x)-Q(x)G(x) R(x)=F(x)Q(x)G(x)

VI operator / (const VI &a, const VI &b) { //多项式除法
    static VI f, g, q; f = a; g = b;
    std::reverse(f.begin(), f.end());
    std::reverse(g.begin(), g.end());
    int n = f.size(), m = g.size();
    q = Inv(g, n - m + 1); f.resize(n - m + 1);
    q = q * f; q.resize(n - m + 1);
    std::reverse(q.begin(), q.end());
    return q;
}
VI operator % (const VI &a, const VI &b) { //多项式取余
    if(a.size() < b.size()) return a;
    static VI c; c = a - (a / b) * b;
    c.resize(b.size() - 1);
    return c;
}
线性递推

给定 a 0 , a 1 ⋯ a k − 1 , f 0 , f 1 ⋯ f k − 1 a_0,a_1\cdots a_{k-1},f_0,f_1\cdots f_{k-1} a0,a1ak1,f0,f1fk1,求前 k k k项为 a a a的递推式 F n = ∑ i = 0 k − 1 f i F n − k + i F_n=\sum_{i=0}^{k-1}f_iF_{n-k+i} Fn=i=0k1fiFnk+i的第 n n n项。
n ≤ 1 0 9 n\le 10^9 n109
推导比较麻烦,这里直接写结论。
G ( x ) ≡ x n ( m o d &ThinSpace;&ThinSpace; x k − f k − 1 x k − 1 ⋯ − f 0 ) G(x)\equiv x^n(\mod x^k-f_{k-1}x^{k-1}\cdots -f_0) G(x)xn(modxkfk1xk1f0)
答案就是 A n s = ∑ i = 0 k − 1 G i a i Ans=\sum_{i=0}^{k-1} G_ia_i Ans=i=0k1Giai
取模的时候快速幂即可。

int Push(const VI &f, const VI &a, int n) { //线性递推 
    static VI mod, r, b; mod = f;
    std::reverse(mod.begin(), mod.end());
    for(int i = 0;i < mod.size(); ++i)
        mod[i] *= -1;
    mod.push_back(1);
    r.resize(1, 1); b.resize(2, 0); b[1] = 1;
    for(;n; b = b * b % mod, n >>= 1)
        if(n & 1) 
            r = r * b % mod;
    long long ans = 0;
    for(int i = 0;i < a.size(); ++i)
        ans += 1LL * r[i] * a[i] % P;
    return fix(ans % P);
}
多项式多点求值

给定 F ( x ) F(x) F(x) a 0 , a 1 ⋯ a m a_0,a_1\cdots a_m a0,a1am,对每个 a i a_i ai F ( a i ) F(a_i) F(ai)
有一个结论是若 R ( x ) ≡ F ( x ) m o d &ThinSpace;&ThinSpace; ( x − a i ) R(x)\equiv F(x)\mod (x-a_i) R(x)F(x)mod(xai),那么 F ( x ) = R ( x ) F(x)=R(x) F(x)=R(x)
这个结论是显然的,因为 F ( x ) = ( x − a i ) Q ( x ) + R ( x ) F(x)=(x-a_i)Q(x)+R(x) F(x)=(xai)Q(x)+R(x),带进去前面消掉了。
考虑分治,构造多项式 P 0 ( x ) = ∏ i = 0 m 2 x − a i P_0(x)=\prod_{i=0}^{\frac{m}{2}}x-a_i P0(x)=i=02mxai, P 1 ( x ) = ∏ i = 0 m 2 + 1 x − a i P_1(x)=\prod_{i=0}^{\frac{m}{2}+1}x-a_i P1(x)=i=02m+1xai
然后用 F ( x ) F(x) F(x) P 0 , P 1 P_0,P_1 P0,P1取模,得到 F 0 , F 1 F_0,F_1 F0,F1,这样我们得到了两个规模减半的相同子问题。递归即可。
P P P要事先预处理。

namespace Value { //多项式多点求值 
    VI res, p[N];
    void Pre(int i, int L, int R, const VI &a) {
        if(L == R) {
            p[i].resize(2); 
            p[i][0] = fix(-a[L]);
            p[i][1] = 1;
            return ;
        }
        int m = L + R >> 1;
        Pre(i << 1, L, m, a); Pre(i << 1 | 1, m + 1, R, a);
        p[i] = p[i << 1] * p[i << 1 | 1];
    }
    void Solve(int i, int L, int R, VI f, const VI &a) {
        if(L == R)
            return res[L] = f[0], void();
        int m = L + R >> 1;
        Solve(i << 1, L, m, f % p[i << 1], a);
        Solve(i << 1 | 1, m + 1, R, f % p[i << 1 | 1], a);
    }
    VI Query(const VI &f, const VI &a) {
        int m = a.size(); res.resize(m);
        Pre(1, 0, m - 1, a);
        Solve(1, 0, m - 1, f % p[1], a);
        return res;
    }
}
多项式快速插值

给定 N N N个点 ( x i , y i ) (x_i,y_i) (xi,yi),求 N − 1 N-1 N1阶拟合多项式。
先复习一下拉格朗日插值的式子吧!
L ( x ) = ∑ i = 0 n − 1 y i ℓ ( i ) L(x)=\sum_{i=0}^{n-1} y_i \ell(i) L(x)=i=0n1yi(i)
其中插值基函数 ℓ ( i ) = ∏ j = 0 , j ≠ i n − 1 x − x j x i − x j \ell(i)=\prod\limits_{j=0,j\neq i}^{n-1}\frac{x-x_j}{x_i-x_j} (i)=j=0,j̸=in1xixjxxj
我们先来考虑求 g i = ∏ j = 0 , j ≠ i n − 1 x i − x j g_i=\prod\limits_{j=0,j\neq i}^{n-1}x_i-x_j gi=j=0,j̸=in1xixj
π ( x ) = ∏ i = 0 n − 1 ( x − x i ) \pi(x)=\prod_{i=0}^{n-1}(x-x_i) π(x)=i=0n1(xxi)
g i = lim ⁡ x → x i π ( x i ) x − x i = π ′ ( x i ) g_i=\lim_{x\to x_i}\frac{\pi(x_i)}{x-x_i}=\pi&#x27;(x_i) gi=limxxixxiπ(xi)=π(xi)
结合多点求值可以得到。
接下来同样考虑分治,设 L l , r ( x ) L_{l,r}(x) Ll,r(x)表示第 l l l r r r个点插值得到的结果, m = l + r 2 m=\frac{l+r}{2} m=2l+r
L l , r ( x ) = ∑ i = l r y i g i ∏ j = l , j ≠ i r x − x j L_{l,r}(x)=\sum\limits_{i=l}^r\frac{y_i}{g_i}\prod\limits_{j=l,j\neq i}^rx-x_j Ll,r(x)=i=lrgiyij=l,j̸=irxxj
= ∑ i = l m y i g i ∏ j = l , j ≠ i m x − x j ∏ j = m + 1 r x − x j + ∑ i = m + 1 r y i g i ∏ j = m + 1 , j ≠ i r x − x j ∏ j = l m x − x j =\sum\limits_{i=l}^m\frac{y_i}{g_i}\prod\limits_{j=l,j\neq i}^mx-x_j\prod\limits_{j=m+1}^rx-x_j+\sum\limits_{i=m+1}^r\frac{y_i}{g_i}\prod\limits_{j=m+1,j\neq i}^rx-x_j\prod\limits_{j=l}^mx-x_j =i=lmgiyij=l,j̸=imxxjj=m+1rxxj+i=m+1rgiyij=m+1,j̸=irxxjj=lmxxj
= L l , m ( x ) ∏ j = m + 1 r x − x j + L m + 1 , r ( x ) ∏ j = l m x − x j =L_{l,m}(x)\prod\limits_{j=m+1}^rx-x_j+L_{m+1,r}(x)\prod\limits_{j=l}^mx-x_j =Ll,m(x)j=m+1rxxj+Lm+1,r(x)j=lmxxj
同前面的套路,预处理出 ∏ j = l r x − x j \prod\limits_{j=l}^rx-x_j j=lrxxj,分治即可。

namespace Lag { //多项式快速插值
	VI g;
	VI Solve(int i, int L, int R, const VI &y) {
		if(L == R) return VI(1, 1LL * y[L] * g[L] % P);
		int m = L + R >> 1;
		return Solve(i << 1, L, m, y) * Value::p[i << 1 | 1] + 
			Value::p[i << 1] * Solve(i << 1 | 1, m + 1, R, y);
	}
	VI Calc(const VI &x, const VI &y) {
		int n = x.size();
		Value::Pre(1, 0, n - 1, x);
		g = deri(Value::p[1]);
		Value::res.resize(n);
		Value::Solve(1, 0, n - 1, g, x);
		for(int i = 0;i < n; ++i)
			g[i] = Inv(Value::res[i]);
		return Solve(1, 0, n - 1, y);
	}
}

代码汇总

luogu模板区改改主程序开O2都能过,但是因为是封装的vector所以常数巨大。

#include<bits/stdc++.h>
const int N = 524288, P = 998244353, inv2 = P + 1 >> 1;
typedef std::vector<int> VI;
int fix(int x) {return (x >> 31 & P) + x;}
int ri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f  = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int mri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f  = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (1LL * x * 10 - '0' + c) % P; return fix(x * f);
}
int InvL, L, R[N], w[N];
int Pow(int x, int k) {
    int r = 1;
    for(;k; k >>= 1, x = 1LL * x * x % P)
        if(k & 1)
            r = 1LL * r * x % P;
    return r;
}
int Inv(int x) {return Pow(x, P - 2);}
void Pre(int m) {
    int x = 0; L = 1;
    for(;(L <<= 1) < m; ++x) ;
    for(int i = 1;i < L; ++i)
        R[i] = R[i >> 1] >> 1 | (i & 1) << x;
    int wn = Pow(3, (P - 1) / L); w[0] = 1;
    for(int i = 1;i < L; ++i)
        w[i] = 1LL * w[i - 1] * wn % P;
    InvL = Inv(L);
}
void NTT(int *F) {
    for(int i = 0;i < L; ++i)
        if(R[i] > i)
            std::swap(F[i], F[R[i]]);
    for(int i = 1, d = L >> 1; i < L; i <<= 1, d >>= 1)
        for(int j = 0;j < L; j += i << 1) {
            int *l = F + j, *r = F + i + j, *p = w, tp;
            for(int k = i; k--; ++l, ++r, p += d)
                tp = 1LL * *p * *r % P, *r = (*l - tp) % P, *l = (*l + tp) % P;
        }
}
void Fill(const VI &a, int *A, int m) {
    m = std::min(m, (int)a.size());
    for(int i = 0;i < m; ++i)
        A[i] = a[i];
    for(int i = m; i < L; ++i)
        A[i] = 0;
}
void Fill(int *A, int *B, int m) {
    for(int i = 0;i < m; ++i)
        B[i] = A[i];
    for(int i = m; i < L; ++i)
        B[i] = 0;
}
VI operator * (const VI &a, const VI &b) {
	const int Lim = 3000;
	int asz = a.size(), bsz = b.size(), m = asz + bsz - 1; 
	static VI c; c.resize(m);
	if(1LL * asz * bsz <= Lim) {
		for(int i = 0;i < m; ++i)	
			c[i] = 0;
		for(int i = 0;i < asz; ++i)
			for(int j = 0;j < bsz; ++j)
				c[i + j] = (c[i + j] + 1LL * a[i] * b[j]) % P;
		return c;
	}
	Pre(m); static int A[N], B[N];
	Fill(a, A, asz); Fill(b, B, bsz);
	NTT(A); NTT(B);
	for(int i = 0;i < L; ++i)
		A[i] = (1LL * A[i] * B[i]) % P;
	NTT(A);
	for(int i = 0;i < m; ++i)
		c[i] = fix(1LL * A[L - i & L - 1] * InvL % P);
	return c;
}
VI operator - (const VI &a, const VI &b) {
    int n = std::max(a.size(), b.size());
    static VI c; c.resize(n, 0);
    for(int i = 0;i < a.size(); ++i)
        c[i] = a[i];
    for(int i = 0;i < b.size(); ++i)
        c[i] = fix(c[i] - b[i]);
    return c;
}
VI operator + (const VI &a, const VI &b) {
    int n = std::max(a.size(), b.size());
    static VI c; c.resize(n, 0);
    for(int i = 0;i < a.size(); ++i)
        c[i] = a[i];
    for(int i = 0;i < b.size(); ++i)
    	c[i] = (c[i] + b[i]) % P;
    return c;
}
VI Inv(const VI &a, const int m) { //多项式求逆
    static int A[N], B[N], C[N];
    for(int i = 0;i < m; ++i)
        A[i] = 0;
    A[0] = Inv(a[0]);
    int n = 1;
    for(;n < m;) {
        Pre(n << 2);
        Fill(a, B, n << 1);
        Fill(A, C, n);
        NTT(B); NTT(C);
        for(int i = 0;i < L; ++i)	
            B[i] = 1LL * B[i] * C[i] % P * C[i] % P;
        NTT(B);
        n <<= 1;
        for(int i = 0;i < n; ++i)
            A[i] = ((A[i] << 1) - 1LL * B[L - i & L - 1] * InvL) % P;
    }
    static VI c; c.resize(m);
    for(int i = 0;i < m; ++i)	
        c[i] = fix(A[i]);
    return c;
}
VI deri(const VI &a) { //多项式求导 
    static VI c; 
    if(a.size() == 1) return VI(1, 0);
    c.resize(a.size() - 1);
    for(int i = 0;i < a.size() - 1; ++i)
        c[i] = 1LL * a[i + 1] * (i + 1) % P;
    return c;
}
VI inter(const VI &a) { //多项式积分 
    static VI c; c.resize(a.size() + 1);
    c[0] = 0;
    for(int i = 0;i < a.size(); ++i)
        c[i + 1] = 1LL * a[i] * Inv(i + 1) % P;
    return c;
}
VI Ln(const VI &a, const int m) { //多项式求Ln 
    static VI c;
    c = deri(a) * Inv(a, m - 1);
    c.resize(m - 1);
    return inter(c);
}
VI Sqrt(const VI &a) { //多项式开根 
    static VI b, c;
    b.resize(1); 
    b[0] = 1; //仅仅在a0=1的时候成立,否则的话要用BSGS 
    int n = 1, m = a.size();
    for(;n < m;) {
        n <<= 1;
        c = Inv(b, n);
        b = b * b; b.resize(n);
        for(int i = 0;i < std::min(n, m); ++i)
            b[i] = 1LL * (b[i] + a[i]) * inv2 % P;
        b = b * c;
        b.resize(n);
    }
    b.resize(m);
    return b;
}
VI Exp(const VI &a, const int m) { //多项式求exp 
    static VI f, g; f.resize(1); f[0] = 1;
    int n = 1, sz = a.size();
    for(;n < m;) {
        n <<= 1; g = Ln(f, n); 
        for(int i = 0;i < n; ++i)
            g[i] = i < sz ? fix(a[i] - g[i]) : fix(-g[i]);
        (++g[0]) %= P;
        f = f * g; f.resize(n);
    }
    f.resize(m);
    return f;
}
VI Pow(const VI &a, int k, int n) { //多项式快速幂 
    static VI f; f = Ln(a, n);
    for(int i = 0;i < n; ++i)
        f[i] = 1LL * f[i] * k % P;
    return Exp(f, n);
}
VI operator / (const VI &a, const VI &b) { //多项式除法
    static VI f, g, q; f = a; g = b;
    std::reverse(f.begin(), f.end());
    std::reverse(g.begin(), g.end());
    int n = f.size(), m = g.size();
    q = Inv(g, n - m + 1); f.resize(n - m + 1);
    q = q * f; q.resize(n - m + 1);
    std::reverse(q.begin(), q.end());
    return q;
}
VI operator % (const VI &a, const VI &b) { //多项式取余
    if(a.size() < b.size()) return a;
    static VI c; c = a - (a / b) * b;
    c.resize(b.size() - 1);
    return c;
}
int Push(const VI &f, const VI &a, int n) { //线性递推 
    static VI mod, r, b; mod = f;
    std::reverse(mod.begin(), mod.end());
    for(int i = 0;i < mod.size(); ++i)
        mod[i] *= -1;
    mod.push_back(1);
    r.resize(1, 1); b.resize(2, 0); b[1] = 1;
    for(;n; b = b * b % mod, n >>= 1)
        if(n & 1) 
            r = r * b % mod;
    long long ans = 0;
    for(int i = 0;i < a.size(); ++i)
        ans += 1LL * r[i] * a[i] % P;
    return fix(ans % P);
}
namespace Value { //多项式多点求值 
    VI res, p[N];
    void Pre(int i, int L, int R, const VI &a) {
        if(L == R) {
            p[i].resize(2); 
            p[i][0] = fix(-a[L]);
            p[i][1] = 1;
            return ;
        }
        int m = L + R >> 1;
        Pre(i << 1, L, m, a); Pre(i << 1 | 1, m + 1, R, a);
        p[i] = p[i << 1] * p[i << 1 | 1];
    }
    void Solve(int i, int L, int R, VI f, const VI &a) {
        if(L == R)
            return res[L] = f[0], void();
        int m = L + R >> 1;
        Solve(i << 1, L, m, f % p[i << 1], a);
        Solve(i << 1 | 1, m + 1, R, f % p[i << 1 | 1], a);
    }
    VI Query(const VI &f, const VI &a) {
        int m = a.size(); res.resize(m);
        Pre(1, 0, m - 1, a);
        Solve(1, 0, m - 1, f % p[1], a);
        return res;
    }
}
namespace Lag { //多项式快速插值
	VI g;
	VI Solve(int i, int L, int R, const VI &y) {
		if(L == R) return VI(1, 1LL * y[L] * g[L] % P);
		int m = L + R >> 1;
		return Solve(i << 1, L, m, y) * Value::p[i << 1 | 1] + 
			Value::p[i << 1] * Solve(i << 1 | 1, m + 1, R, y);
	}
	VI Calc(const VI &x, const VI &y) {
		int n = x.size();
		Value::Pre(1, 0, n - 1, x);
		g = deri(Value::p[1]);
		Value::res.resize(n);
		Value::Solve(1, 0, n - 1, g, x);
		for(int i = 0;i < n; ++i)
			g[i] = Inv(Value::res[i]);
		return Solve(1, 0, n - 1, y);
	}
}
int main() {
    return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值