一篇自己都看不懂的多项式运算学习笔记

发现多项式运算学了一会儿不用就忘了……

多项式运算学习笔记

1、前置芝士

1、多项式乘法(这个还没准备写呢稍微延后一点写咕咕咕

2、多项式求导&(不定)积分(参见高中数学选修2-2&高等数学

默认\(C\)表示常数,\(f(x)\)\(g(x)\)表示函数,在多项式运算中大概只需要用到以下导数相关的公式:

\((x^a)' = ax^{a-1}\)\(C' = 0\)\((Cf(x))' = Cf'(x)\)\((f(x) + g(x))' = f'(x) + g'(x)\)

可以从上面得到\(F(x) = \sum\limits_{i=0}^n a_ix^i\)的导函数为\(F'(x) = \sum\limits_{i=1}^n a_iix^{i-1}\)

不定积分就是求导的逆运算,即如果\(F'(x) = \sum\limits_{i=0}^n a_ix^i\),则有\(F(x) = C + \sum\limits_{i=1}^{n+1} \frac{a_{i-1}}{i}x^i\)。注意到\(F(x)\)的常数项可以是任意值,我们一般以\(F(0)\)的值或者\(F(x)\)的实际意义确定\(F(x)\)的常数项,这里默认为\(0\)

    void getDif(int *a , int len){//求导
        for(int i = 0 ; i < len - 1 ; ++i)
            a[i] = 1ll * a[i + 1] * (i + 1) % MOD;
        a[len - 1] = 0;
    }

    void getInt(int *a , int len){//求不定积分
        for(int i = len - 1 ; i ; --i)
            a[i] = 1ll * a[i - 1] * _inv[i] % MOD;
        a[0] = 0;
    }

3、泰勒展开&牛顿迭代:

对于一个函数\(F(x)\),如果它在\(x = x_0\)处有定义且有\(n\)阶导数,那么由泰勒展开可以得到

\[F(x) = \sum\limits_{i=0}^n \frac{F^{(i)}(x_0)}{i!}(x-x_0)^i + R_n(x)\]

其中\(F^{(i)}(x_0)\)表示\(F(x)\)\(i\)阶导数在\(x=x_0\)处的取值,\(R_n(x)\)一般认为是一个可以忽略不计的值。

牛顿迭代基于泰勒展开,给出了一种能够通过倍增解决多项式运算问题的较通用方法:

假设我们现在需要求满足\(F(G(x)) \equiv 0 \mod x^n\)\(G(x)\),值得注意的是函数\(F\)的自变量是一个多项式\(G(x)\)。而我们现在已经得到了满足\(F(G_1(x)) \equiv 0 \mod x^{\lceil \frac{n}{2} \rceil}\)的多项式\(G_1(x)\)。我们将函数\(F(G(x))\)\(G(x) = G_1(x)\)处泰勒展开,可以得到:

\[F(G(x)) \equiv F(G_1(x)) + F'(G_1(x))(G(x) - G_1(x)) + \sum\limits_{i=2}^\infty \frac{F^{(i)}(G_1(x))}{i!}(G(x) - G_1(x))^i \mod x^n\]

注意到\(F(G(x)) \equiv 0 \mod x^n\),即\(F(G(x)) \equiv \mod x^{\lceil \frac{n}{2} \rceil}\),而多项式运算中高次项不影响低次项,故\(G_1(x)\)\(G(x)\)的最低\(\lceil \frac{n}{2} \rceil\)项系数相同。所以\(G(x) - G_1(x) \equiv 0 \mod x^{\lceil \frac{n}{2} \rceil}\),即\((G(x) - G_1(x))^2 \equiv 0 \mod x^n\),所以可以直接把上式右边的一大堆求和丢掉,直接变为

\[F(G(x)) \equiv F(G_1(x)) + F'(G_1(x))(G(x) - G_1(x)) \equiv 0 \mod x^n\]

\[G(x) = G_1(x) - \frac{F(G_1(x))}{F'(G_1(x))} \mod x^n\]

这样我们可以从\(\mod x^n\)意义下的问题转为解决\(\mod x^{\lceil \frac{n}{2} \rceil}\)意义下的问题,减小了一半的问题规模。所以只需要在\(\mod x^1\)的边界条件下问题可以解决,就可以解决\(\mod x^n\)意义下的多项式运算了。

注意到上面牛顿迭代中似乎有一个\(\frac{1}{F'(H(x))}\)的东西。除多项式是个什么鬼东西啊qwq……所以接下来进入正题:

2、多项式运算——多项式求逆

给出\(F(x)\),求\(G(x)\)满足\(F(x)G(x) \equiv 1 \mod x^n\),即计算\(G(x) = \frac{1}{F(x)}\)

这个似乎因为牛顿迭代本身就要多项式求逆所以没法牛顿迭代解,但是我们仍然可以考虑倍增。

假设我们求得了满足\(F(x)H(x) \equiv 1 \mod x^{\lceil \frac{n}{2} \rceil}\)的多项式\(H(x)\),那么有

\[G(x) - H(x) \equiv 0 \mod x^{\lceil \frac{n}{2} \rceil}\]

\[(G(x) - H(x))^2 = G^2(x) - 2G(x)H(x) + H^2(x) \equiv 0 \mod x^n\]

两边同乘\(F(x)\),因为\(F(x)G(x) \equiv 1 \mod x^n\),所以可以得到

\[G(x)-2H(x)+F(x)H^2(x) \equiv 0 \mod x^n\]

\[G(x) \equiv 2H(x) - F(x)H^2(x) \mod x^n\]

\(\mod x^1\)下就是求逆元。

    void getInv(int *a , int *b , int len){
        if(len == 1){//如果模x^1则直接求逆元
            b[0] = poww(a[0] , MOD - 2);
            return;
        }
        getInv(a , b , (len + 1) >> 1);//倍增递归
        memcpy(A , a , sizeof(int) * len);
        memcpy(B , b , sizeof(int) * len);
        init(len * 3);
        NTT(A , 1);NTT(B , 1);//计算
        for(int i = 0 ; i < need ; ++i)
            A[i] = 1ll * A[i] * B[i] % MOD * B[i] % MOD;
        NTT(A , -1);
        for(int i = 0 ; i < len ; ++i)
            b[i] = (2ll * b[i] - 1ll * A[i] * inv % MOD + MOD) % MOD;
        clear(A);clear(B);//记得清空数组
    }

3、多项式运算——多项式ln

给出\(F(x)\),保证其常数项为\(1\),求\(G(x)\)满足\(G(x) \equiv ln\ F(x) \mod x^n\)

写到这里突然想到要加两条导数有关的式子:\((ln\ x)' = \frac{1}{x}\),以及对于复合函数\(f(g(x))\),有\(f(g(x))' = f'(g(x))g'(x)\)

\(G(x) = ln\ F(x)\)两边求导得到

\[G'(x) = \frac{F'(x)}{F(x)}\]

多项式求导+求逆+积分即可。

注意因为\(F(0) = 1 , ln\ 1 = 0,\)所以\(G(x)\)的常数项为\(0\)

    void getLn(int *a , int *b , int len){
        getInv(a , C , len);
        memcpy(A , a , sizeof(int) * len);
        getDif(A , len);
        init(len * 2);
        mul(A , C);
        for(int i = 0 ; i < len ; ++i)
            b[i] = 1ll * A[i] * inv % MOD;
        getInt(b , len);
        clear(A);clear(C);
    }

4、多项式运算——多项式exp

给出\(F(x)\),保证其常数项为\(0\),求\(G(x)\)满足\(G(x) = e^{F(x)} \mod x^n\)

这个运算在指数型生成函数中经常用到。第一反应是求导,有\(ln\ G(x) \equiv F(x) \mod x^n\)

这个时候请出前置芝士——牛顿迭代上场

构造多项式函数\(H(G(x)) = F(x) - ln\ G(x)\),代入牛顿迭代式子有\(G(x) = G_1(x) - \frac{H(G_1(x))}{H'(G_1(x))}\)。而\(H'(G_1(x)) = -\frac{1}{G_1(x)}\),所以

\[G(x) = G_1(x)(1 + F(x) - ln\ G_1(x))\]

多项式求\(ln\)即可

    void getExp(int *a , int *b , int len){
        if(len == 1){
            b[0] = 1;//e^0=1
            return;
        }
        getExp(a , b , (len + 1) >> 1);
        getLn(b , D , len);
        for(int i = 0 ; i < len ; ++i)
            D[i] = (MOD + (!i) - D[i] + a[i]) % MOD;
        memcpy(A , b , sizeof(int) * len);
        init(len * 2);
        mul(A , D);
        for(int i = 0 ; i < len ; ++i)
            b[i] = 1ll * A[i] * inv % MOD;
        clear(A);clear(D);
    }

5、多项式运算——多项式带余除法

给定两个多项式\(F(x),G(x)\),最高项分别为\(n,m(n \geq m)\),求一个\(n-m\)次多项式\(P(x)\)和一个次数\(< m\)的多项式\(Q(x)\),满足\(F(x) = P(x)G(x) + Q(x)\)

注意到\(x^nF(\frac{1}{x})\)等于把\(F(x)\)的所有项系数翻转过来,不妨记作\(rev(F)\)

\(F(x) = P(x) G(x) + Q(x)\)

\[x^n F(\frac{1}{x}) = x^mG(\frac{1}{x})x^{n-m}P(\frac{1}{x}) + x^n Q(x)\]

\[rev(F) = rev(G)rev(P) + x^{n-m+1} rev(Q) \mod x^{n-m+1}\]

(偷偷加一个模数显然不会影响它们相等)

\[rev(P) = \frac{rev(F)}{rev(G)} \mod x^{n-m+1}\]

多项式求逆得到\(P\),然后用\(F(x) - G(x)P(x)\)得到\(Q(x)\)

    void getDiv(int *a , int *b , int *c , int lenA , int lenB){
        memcpy(C , b , sizeof(int) * lenB);
        reverse(C , C + lenB);
        getInv(C , D , lenA - lenB + 1);//注意这里是模x^(n-m+1)意义下的
        memcpy(C , a , sizeof(int) * lenA);
        reverse(C , C + lenA);
        init(2 * lenA);
        mul(C , D);
        for(int i = 0 ; i <= lenA - lenB ; ++i)
            c[i] = 1ll * C[i] * inv % MOD;
        reverse(c , c + lenA - lenB + 1);
        clear(C);clear(D);
    }

    void getMod(int *a , int *b , int *c , int lenA , int lenB){
        getDiv(a , b , d , lenA , lenB);
        memcpy(A , b , sizeof(int) * lenB);
        memcpy(B , d , sizeof(int) * (lenA - lenB + 1));
        init(lenA + 1);
        mul(A , B);
        for(int i = 0 ; i < lenB - 1 ; ++i)
            c[i] = (a[i] - 1ll * A[i] * inv % MOD + MOD) % MOD;
        clear(A);clear(B);
    }

6、多项式运算——多项式开根

给定\(F(x)\),默认常数项为\(1\),求多项式\(G(x)\)满足\(G(x) \equiv \sqrt{F(x)} \mod x^n\)

不排除能够用多项式快速幂的思路去做,但多项式快速幂常数大到爆炸,所以仍然推荐牛顿迭代

构造多项式函数\(H(G(x)) = F(x) - G^2(x)\),代入牛顿迭代公式得\[G(x) = G_1(x) - \frac{F(x) - G_1^2(x)}{-2G_1(x)} = \frac{1}{2}(G_1(x) + \frac{F(x)}{G_1(x)})\]

如果\(F(x)\)常数项不为\(1\)就要求二次剩余。

    void getSqrt(int *a , int *b , int len){
        if(len == 1){
            b[0] = 1;
            return;
        }
        getSqrt(a , b , (len + 1) >> 1);
        getInv(b , C , len);
        memcpy(A , a , sizeof(int) * len);
        init(len * 2);
        mul(A , C);
        for(int i = 0 ; i < len ; ++i)
            b[i] = _inv[2] * (b[i] + 1ll * A[i] * inv % MOD) % MOD;
        clear(A);clear(C);
    }

7、多项式运算——多项式快速幂

给定\(K,F(x)\),求\(G(x)\)满足\(G(x) \equiv F^K(x) \mod x^n\)

两边求导有

\[ln\ G(x) \equiv K\ ln\ F(x) \mod x^n\]

\(ln\)、给每一位乘\(K\)然后\(exp\)回来就行了

注意\(K\)的取模对象是模数而不是模数的欧拉函数值。

    void getPow(int *a , int *b , int len , int K){
        getLn(a , E , len);
        for(int i = 0 ; i < len ; ++i)
            E[i] = 1ll * E[i] * K % MOD;
        getExp(E , b , len);
        memset(E , 0 , sizeof(int) * len);
    }

8、模板题——LOJ150 挑战多项式

这道题除了多项式带余除法基本上全部涉及到了……还需要求二次剩余,不了解的请自行百度

写这道题会让你发现你有多少数组计算过程中发生了重合……果然还是vector好用……

代码如下

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<vector>
#include<cmath>
#include<random>
#include<cassert>
//This code is written by Itst
using namespace std;

const int mod = 998244353;
inline int read(bool flg = 0){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c) && c != EOF){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    if(c == EOF)
        exit(0);
    while(isdigit(c)){
        if(flg)
            a = (a * 10ll + c - 48) % mod;
        else
            a = a * 10 + c - 48;
        c = getchar();
    }
    if(flg) a += mod;
    return f ? -a : a;
}

const int MAXN = (1 << 19) + 7 , MOD = 998244353;
#define PII pair < int , int >
#define st first
#define nd second

PII mul(PII a , PII b , int val){
    return PII((1ll * a.st * b.st + 1ll * a.nd * b.nd % MOD * val) % MOD , (1ll * a.st * b.nd + 1ll * a.nd * b.st) % MOD);
}

int poww(PII a , int b , int val){//二次剩余——虚数快速幂
    PII cur = PII(1 , 0);
    while(b){
        if(b & 1) cur = mul(cur , a , val);
        a = mul(a , a , val);
        b >>= 1;
    }
    return cur.st;
}

inline int poww(long long a , int b){
    int times = 1;
    while(b){
        if(b & 1)
            times = times * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return times;
}

int calc_Surplus(int x){//二次剩余
    mt19937 rnd(time(0));
    int a = rnd() % MOD;
    while(poww((1ll * a * a - x + MOD) % MOD , (MOD - 1) / 2) != MOD - 1)
        a = rnd() % MOD;
    return poww(PII(a , 1) , (MOD + 1) / 2 , (1ll * a * a - x + MOD) % MOD);
}

namespace poly{
    const int G = 3 , INV = (MOD + 1) / G;
    int A[MAXN] , B[MAXN] , C[MAXN] , D[MAXN] , E[MAXN];
    int a[MAXN] , b[MAXN] , c[MAXN] , d[MAXN];
    int need , inv , dir[MAXN] , _inv[MAXN];
#define clear(x) memset(x , 0 , sizeof(int) * need)
    
    void init(int len){//初始化NTT
        need = 1;
        while(need < len)
            need <<= 1;
        inv = poww(need , MOD - 2);
        for(int i = 1 ; i < need ; ++i)
            dir[i] = (dir[i >> 1] >> 1) | (i & 1 ? need >> 1 : 0);
    }

    void init_inv(){//初始化逆元
        _inv[1] = 1;
        for(int i = 2 ; i < MAXN ; ++i)
            _inv[i] = MOD - 1ll * (MOD / i) * _inv[MOD % i] % MOD;
    }

    void NTT(int *arr , int type){
        for(int i = 1 ; i < need ; ++i)
            if(i < dir[i])
                arr[i] ^= arr[dir[i]] ^= arr[i] ^= arr[dir[i]];
        for(int i = 1 ; i < need ; i <<= 1){
            int wn = poww(type == 1 ? G : INV , (MOD - 1) / i / 2);
            for(int j = 0 ; j < need ; j += i << 1){
                long long w = 1;
                for(int k = 0 ; k < i ; ++k , w = w * wn % MOD){
                    int x = arr[j + k] , y = arr[i + j + k] * w % MOD;
                    arr[j + k] = x + y >= MOD ? x + y - MOD : x + y;
                    arr[i + j + k] = x < y ? x + MOD - y : x - y;
                }
            }
        }
    }

    void mul(int *a , int *b){//乘法
        NTT(a , 1);NTT(b , 1);
        for(int i = 0 ; i < need ; ++i)
            a[i] = 1ll * a[i] * b[i] % MOD;
        NTT(a , -1);
    }
    
    void getDif(int *a , int len){//求导
        for(int i = 0 ; i < len - 1 ; ++i)
            a[i] = 1ll * a[i + 1] * (i + 1) % MOD;
        a[len - 1] = 0;
    }

    void getInt(int *a , int len){//求不定积分
        for(int i = len - 1 ; i ; --i)
            a[i] = 1ll * a[i - 1] * _inv[i] % MOD;
        a[0] = 0;
    }

    void getInv(int *a , int *b , int len){//求逆
        if(len == 1){
            b[0] = poww(a[0] , MOD - 2);
            return;
        }
        getInv(a , b , (len + 1) >> 1);
        memcpy(A , a , sizeof(int) * len);
        memcpy(B , b , sizeof(int) * len);
        init(len * 3);
        NTT(A , 1);NTT(B , 1);
        for(int i = 0 ; i < need ; ++i)
            A[i] = 1ll * A[i] * B[i] % MOD * B[i] % MOD;
        NTT(A , -1);
        for(int i = 0 ; i < len ; ++i)
            b[i] = (2 * b[i] - 1ll * A[i] * inv % MOD + MOD) % MOD;
        clear(A);clear(B);
    }
    
    void getDiv(int *a , int *b , int *c , int lenA , int lenB){//求带余除法的商(本题不需要)
        memcpy(C , b , sizeof(int) * lenB);
        reverse(C , C + lenB);
        getInv(C , D , lenA - lenB + 1);
        memcpy(C , a , sizeof(int) * lenA);
        reverse(C , C + lenA);
        init(2 * lenA);
        mul(C , D);
        for(int i = 0 ; i <= lenA - lenB ; ++i)
            c[i] = 1ll * C[i] * inv % MOD;
        reverse(c , c + lenA - lenB + 1);
        clear(C);clear(D);
    }

    void getMod(int *a , int *b , int *c , int lenA , int lenB){//求带余除法的余数(本题不需要)
        getDiv(a , b , d , lenA , lenB);
        memcpy(A , b , sizeof(int) * lenB);
        memcpy(B , d , sizeof(int) * (lenA - lenB + 1));
        init(lenA + 1);
        mul(A , B);
        for(int i = 0 ; i < lenB - 1 ; ++i)
            c[i] = (a[i] - 1ll * A[i] * inv % MOD + MOD) % MOD;
        clear(A);clear(B);
    }
    
    void getLn(int *a , int *b , int len){//求ln
        getInv(a , C , len);
        memcpy(A , a , sizeof(int) * len);
        getDif(A , len);
        init(len * 2);
        mul(A , C);
        for(int i = 0 ; i < len ; ++i)
            b[i] = 1ll * A[i] * inv % MOD;
        getInt(b , len);
        clear(A);clear(C);
    }

    void getExp(int *a , int *b , int len){//求exp
        if(len == 1){
            b[0] = 1;
            return;
        }
        getExp(a , b , (len + 1) >> 1);
        getLn(b , D , len);
        for(int i = 0 ; i < len ; ++i)
            D[i] = (MOD + (!i) - D[i] + a[i]) % MOD;
        memcpy(A , b , sizeof(int) * len);
        init(len * 2);
        mul(A , D);
        for(int i = 0 ; i < len ; ++i)
            b[i] = 1ll * A[i] * inv % MOD;
        clear(A);clear(D);
    }

    void getSqrt(int *a , int *b , int len){//开根
        if(len == 1){
            b[0] = calc_Surplus(a[0]);
            if(MOD - b[0] < b[0]) b[0] = MOD - b[0];
            return;
        }
        getSqrt(a , b , (len + 1) >> 1);
        getInv(b , C , len);
        memcpy(A , a , sizeof(int) * len);
        init(len * 2);
        mul(A , C);
        for(int i = 0 ; i < len ; ++i)
            b[i] = _inv[2] * (b[i] + 1ll * A[i] * inv % MOD) % MOD;
        clear(A);clear(C);
    }

    void getPow(int *a , int *b , int len , int K){//快速幂
        getLn(a , E , len);
        for(int i = 0 ; i < len ; ++i)
            E[i] = 1ll * E[i] * K % MOD;
        getExp(E , b , len);
    memset(E , 0 , sizeof(int) * len);
    }
}
using namespace poly;
int F[MAXN];

int main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    init_inv();
    int N = read() + 1 , K = read();
    for(int i = 0 ; i < N ; ++i) F[i] = a[i] = read();
    getSqrt(a , b , N);
    getInv(b , c , N);
    getInt(c , N);
    getExp(c , d , N);
    for(int i = 0 ; i < N ; ++i)
        a[i] = ((!i) * 2 + (bool)i * F[i] - d[i] + MOD) % MOD;
    memset(b , 0 , sizeof(int) * N);
    getLn(a , b , N);
    memset(c , 0 , sizeof(int) * N);
    b[0] = 1;
    getPow(b , c , N , K);
    getDif(c , N);
    for(int i = 0 ; i < N - 1 ; ++i)
        printf("%d " , c[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Itst/p/10544566.html