模板:基於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) {
    int m = a.size() + b.size() - 1;
    static VI c; static int A[N], B[N];
    c.resize(m, 0); 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;
}

迭代相關:多項式求逆,開根,求exp

這些我們熟悉的多項式操作都是採用迭代法解決,既然都是迭代法,必有其共性,因此下面便脫離傳統的證明方法,採用牛頓迭代法來進行一波推導

知識點:泰勒展開

下面首先介紹一下泰勒展開:
對於一個函數f(x)f(x),其在x=x0x=x_0處的泰勒公式如下:
f(x)=f(x0)0!+f(x0)1!(xx0)+f(x0)2!(xx0)2f(n)(x0)n!(xx0)nf(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)f(x),我們考慮以xix_i作爲起點逼近其零點。
方法就是,作(xi,yi)(x_i,y_i)處的切線,設其交xx軸於xi+1x_{i+1},以xi+1x_{i+1}作爲下一次的起點重複上述過程不斷逼近零點。
推一推式子:
yf(xi)=f(xi)(xxi)y-f(x_i)=f&#x27;(x_i)(x-x_i)
y=0y=0,得到xi+1=xif(xi)f(xi)x_{i+1}=x_i-\frac{f(x_i)}{f&#x27;(x_i)}
有沒有發現式子其實挺熟悉的?
考慮直線方程:y=f(xi)+f(xi)(xxi)y=f(x_i)+f&#x27;(x_i)(x-x_i)
發現直線方程就是f(x)f(x)xix_i處的一階泰勒展開。
形式化地,我們可以這樣描述牛頓迭代法:
用函數f(x)f(x)f(xi)f(x_i)處的一階展開方程的零點作爲下一次迭代的起點xix_i,這樣的迭代法就是牛頓迭代法。
這個東西對於多項式有什麼好處呢?
我們不難發現定義一個以多項式爲自變量的函數G(x)G(x),泰勒公式仍然成立。
我們首先將題目的形式轉化爲求F(x)G(F(x))0(mod&ThinSpace;&ThinSpace;xn)F(x)|G(F(x))\equiv 0(\mod x^n)
迭代法的一般考慮步驟是:
已知G(F0(x))0(mod&ThinSpace;&ThinSpace;xn)G(F_0(x))\equiv 0(\mod x^n)
G(F(x))0(mod&ThinSpace;&ThinSpace;x2n)G(F(x))\equiv 0 (\mod x^{2n})
考慮G(x)G(x)F0(x)F_0(x)處的泰勒展開逼近F(x)F(x)
G(F(x))G(F0(x))+G(F0(x))(F(x)F0(x))+G(F0(x))(F(x)F0(x))2(mod&ThinSpace;&ThinSpace;xn)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)
事實上,我們有F(x)F0(x)(mod&ThinSpace;&ThinSpace;xn)F(x)\equiv F_0(x) (\mod x^n)
那麼F(x)F0(x)0(mod&ThinSpace;&ThinSpace;xn)(F(x)F0(x))20(mod&ThinSpace;&ThinSpace;x2n)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)F(x)-F_0(x)的最低次冪是xnx^n,平方之後最低次冪就變成了x2nx^{2n}
於是我們有G(F(x))G(F0(x))+G(F0(x))(F(x)F0(x))(mod&ThinSpace;&ThinSpace;x2n)G(F(x))\equiv G(F_0(x))+G&#x27;(F_0(x))(F(x)-F_0(x)) (\mod x^{2n})
也就是說,F(x)F(x)F0(x)F_0(x)牛頓迭代的結果。
根據牛頓迭代的結果,我們可以直接得到:
F(x)=F0(x)G(F0(x))G(F0(x))F(x)=F_0(x)-\frac{G(F_0(x))}{G&#x27;(F_0(x))}
基於這個結論,我們可以採用類似倍增的方法來求得最終需要的F(x))F(x))

多項式求逆

已知F(x)F(x),求R(x)F(x)R(x)1(mod&ThinSpace;&ThinSpace;xn)R(x)|F(x)R(x)\equiv 1(\mod x^n)
原式等價於
F(x)R1(x)0(mod&ThinSpace;&ThinSpace;xn)F(x)-R^{-1}(x)\equiv 0(\mod x^n)
注意構造的時候一般讓F(x)F(x)在常數位置上,這樣求導的時候直接消掉
構造函數G(R(x))=F(x)R1(x)G(R(x))=F(x)-R^{-1}(x)
求導G(R(x))=R2(x)G&#x27;(R(x))=-R^{-2}(x)
假設求得G(R0(x))0(mod&ThinSpace;&ThinSpace;xn)G(R_0(x))\equiv 0(\mod x^n)
由剛纔的結論R(x)R0(x)G(R0(x))G(R0(x))R0(x)F(x)R01(x)R02(x0)2R0(x)F(x)R02(x)(mod&ThinSpace;&ThinSpace;x2n)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})
初值直接費馬小定理。
結合多項式乘法,結束了!
複雜度由主定理得到T(n)=T(n2)+O(nlogn)=O(nlogn)T(n)=T(\frac{n}{2})+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),求B(x)B(x)F(x)(mod&ThinSpace;&ThinSpace;xn)B(x)|B(x)\equiv \sqrt {F(x)}(\mod x^n)
同樣地讓F(x)F(x)在常數位上
B2(x)F(x)0(mod&ThinSpace;&ThinSpace;xn)B^2(x)-F(x)\equiv 0(\mod x^n)
構造函數G(B(x))=B2(x)F(x)G(B(x))=B^2(x)-F(x)
求導G(B(x))=2B(x)G&#x27;(B(x))=2B(x)
迭代B(x)=B0(x)G(B0(x))G(B0(x))=B0(x)B02(x)F(x)2B0(x)=F(x)+B02(x)2B0(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&#x27;_0(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)Ln(F(x))(mod&ThinSpace;&ThinSpace;xn)A(x)\equiv Ln(F(x))(\mod x^n)
兩邊同時xx求導(注意和之前對多項式求導不一樣!)
A(x)F(x)F(x)A&#x27;(x)\equiv \frac{F&#x27;(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),求A(x)A(x)eF(x)(mod&ThinSpace;&ThinSpace;xn)A(x)|A(x)\equiv e^ {F(x)}(\mod x^n)
F(x)lnA(x)(mod&ThinSpace;&ThinSpace;xn)F(x) - lnA(x) \equiv(\mod x^n)
構造函數G(A(x))=F(x)ln(A(x))G(A(x))=F(x)-ln(A(x))
求導G(B(x))=A1(x)G&#x27;(B(x))=A^{-1}(x)
迭代A(x)=A0(x)G(A0(x))G(A0(x))=A0(x)F(x)ln(A(x))A01(x)=A0(x)(1F(x)+ln(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)))
初值一般爲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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章