多项式全家桶(但还没学明白,这篇笔记也还没完工)

· · 算法·理论

XK 的课件(其中可能有一些小错误)

stO XK Orz

递归 FFT

#include <bits/stdc++.h>
//#define int long long
#define Comp complex < double > //
using namespace std;
namespace IO
{
    int read()
    {
        int f = 1, x = 0;
        char c = getchar();
        while(c < '0' || c > '9'){
            if(c == '-') f = - 1;
            c = getchar();
        }
        while(c >= '0' && c <= '9'){
            x = x * 10 + c - '0';
            c = getchar();
        }
        return f * x;
    }
    void write(int x)
    {
        if(x < 0){
            putchar('-');
            x = - x;
        }
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;
// Fast IO sometimes -> Slow IO
const int N = (1 << 21);
const double PI = acos(- 1); // 不知道为什么
Comp ff[N + 1], g[N + 1];
void DFT(Comp * f, int n, int rev)
{
    if(n == 1) return;
    Comp f1[n / 2], f2[n / 2];
    for(int i = 0; i < n / 2; i ++){
        f1[i] = f[i * 2];
        f2[i] = f[i * 2 + 1];
    }
    DFT(f1, n / 2, rev);
    DFT(f2, n / 2, rev);
    Comp wk(1, 0), w1(cos((double)PI * 2 / n), sin((double)PI * 2 / n) * (double)rev); //
    // 为什么用下面注释掉的,不用后面没注释掉的,就不对?
//  int i;
//  for(i = 0; i < n / 2; i ++){
//      f[i] = f1[i] + wk * f2[i];
//      wk *= w1;
//  }
//  for(; i < n; i ++){
//      f[i] = f[i - n / 2] + wk * f2[i - n / 2];
//      wk *= w1;
//  }
    for(int i = 0; i < n / 2; i ++){
        f[i] = f1[i] + wk * f2[i];
        f[i + n / 2] = f1[i] - wk * f2[i]; //
        wk *= w1;
    }
}
void solve()
{
    int n, m;
    n = read(); m = read();
    for(int i = 0; i <= n; i ++){
        double tmp = read();
        ff[i].real(tmp);
    }
    for(int i = 0; i <= m; i ++){
        double tmp = read();
        g[i].real(tmp);
    }
    int len = 1;
    while(len < n + m + 1) len <<= 1; // (n + m) 次,一共 (n + m + 1) 项(?)
    DFT(ff, len, 1);
    DFT(g, len, 1);
    for(int i = 0; i < len; i ++) ff[i] *= g[i];
    DFT(ff, len, - 1);
    for(int i = 0; i <= n + m; i ++) printf("%.0lf ", 1e-7 + ff[i].real() / (double)len); // 为什么要加一个很小的数?为了补上丢失的精度?
    putchar('\n');
}
signed main()
{
    int t = 1;
    while(t --) solve();
    return 0;
}
// 递归 FFT

非递归 FFT

#include <bits/stdc++.h>
#define Complex complex < double > //
#define PI acos(-1) // ???
using namespace std;
const int L = (1 << 21); // ?
Complex f[L], g[L];
int rev[L]; // ?
void Change(Complex y[], int len)
{
    for(int i = 0; i < len; ++ i){
        rev[i] = rev[i >> 1] >> 1;
        if(i & 1) rev[i] |= (len >> 1);
    }
    for(int i = 0; i < len; ++ i) if(i < rev[i]) swap(y[i], y[rev[i]]);
}
void DFT(Complex y[], int len, int Rev)
{
    Change(y, len);
    for(int h = 2; h <= len; h <<= 1){
        Complex wn(cos(2 * PI / h), Rev * sin(2 * PI / h)); //
        for(int j = 0; j < len; j += h){
            Complex w(1, 0);
            for(int k = j; k < j + h / 2; k ++){
                Complex u = y[k], t = w * y[k + h / 2];
                y[k] = u + t; y[k + h / 2] = u - t;
                w *= wn;
            }
        }
    }
}
int main()
{
    int n, m; scanf("%d%d", & n, & m);
    for(int i = 0; i <= n; i ++){
        int tmp; scanf("%d", & tmp);
        Complex t(tmp, 0);
        f[i] = t;
    }
    for(int i = 0; i <= m; i ++){
        int tmp; scanf("%d", & tmp);
        Complex t(tmp, 0);
        g[i] = t;
    }
    int len = (1 << ((int)ceil(log2((n + m + 1))))); // ?
    DFT(f, len, 1);
    DFT(g, len, 1);
    for(int i = 0; i < len; i ++) f[i] *= g[i]; // ???
    DFT(f, len, - 1);
    for(int i = 0; i < len; i ++) f[i] /= len; // ???
    for(int i = 0; i < n + m + 1; i ++) printf("%.0lf ", (f[i].real() + 1e-7));
    return 0;
}

非递归 NTT

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int L = (1 << 21), P = 998244353, G = 3; // ?
int f[L], g[L];
int rev[L]; // ?
int Power(int x, int y, int p)
{
    int res = 1; // 是 1 而非 0
    while(y){
        if(y & 1) res = res * x % p;
        x = x * x % p;
        y >>= 1;
    }
    return res;
}
void Change(int y[], int len)
{
    for(int i = 0; i < len; ++ i){
        rev[i] = rev[i >> 1] >> 1;
        if(i & 1) rev[i] |= (len >> 1);
    }
    for(int i = 0; i < len; ++ i) if(i < rev[i]) swap(y[i], y[rev[i]]);
}
void NTT(int y[], int len, int Rev)
{
    Change(y, len);
    for(int h = 2; h <= len; h <<= 1){
        int wn = Power(G, (P - 1) / h, P); // 是 / h 而不是 / len
        if(Rev == - 1) wn = Power(wn, P - 2, P); // ?
        for(int j = 0; j < len; j += h){
            int w = 1;
            for(int k = j; k < j + h / 2; k ++){
                int u = y[k], t = w * y[k + h / 2] % P;
                y[k] = (u + t) % P; y[k + h / 2] = (u - t + P) % P;
                w = w * wn % P;
            }
        }
    }
}
signed main()
{
    int n, m; scanf("%lld%lld", & n, & m);
    for(int i = 0; i <= n; i ++) scanf("%lld", & f[i]); //
    for(int i = 0; i <= m; i ++) scanf("%lld", & g[i]); //
    int len = (1 << ((int)ceil(log2((n + m + 1))))); // ?
    NTT(f, len, 1);
    NTT(g, len, 1);
    for(int i = 0; i < len; i ++) f[i] = f[i] * g[i] % P; // ???
    NTT(f, len, - 1);
    int inv = Power(len, P - 2, P); // 把下一句中的 inv 拿到这里算会快不少
    for(int i = 0; i < n + m + 1; i ++) f[i] = f[i] * inv % P; // 要取模
    for(int i = 0; i < n + m + 1; i ++) printf("%lld ", f[i]);
    return 0;
}

加法

略。

乘法

略。

求逆

F(x)G(x) \equiv 1 \pmod {x ^ n}

G(x)

sol:

牛顿迭代的一个重要式子:

F_{k + 1}(x) \equiv F_{k}(x) - \frac { G(F_{k}(x)) } { G'(F_{k}(x)) } \pmod { x ^ { 2 n } }

每次 n 变成 2 n。于是可以倍增。

还要考虑一开始的 F_0(x)。令(?) F_0(x) = [x^0]F(x),算 f_0 即可(f_0[x^0]F(x))。

据 lr:

( x ^ n ) ' = n x ^ { n - 1 } ( n \in Z )

不确定是不是 n \in Z,是不是 n \in R

现在来求逆。

F(x)G(x) \equiv 1 \pmod {x ^ n} 化成 \frac {1} {G(x)} - F(x) \equiv 0 \pmod { x ^ n } 的形式。

$设 H(G) = \frac {1} {G} - F$。 把上面牛顿迭代的那个式子拿过来用。 技巧:求导时 把 $G$ 当成自变量(?) $x$,把 $F$ 当成常数 $a$。 牛顿迭代(倍增)即可。 技巧:先转成点的形式,拿点来运算,算完了再转回系数的形式。from:[模板题的一篇题解](https://www.luogu.com.cn/article/gnbg6aap)。 时间 $O(n \log n)$。 ```cpp ``` ### 取模 用到了一个技巧:把 $x$ 变成 $\frac {1}{x}$,然后把整个多项式乘以 $x$ 的……次方,就得到了一个把系数翻转过来的多项式。 需要用到求逆。 ### 求导 & 积分 和暴力多项式全家桶里一样的做法。 ### 开根 直接用牛顿迭代做。(和求逆的方法一样) 关于 $g _ 0$,同暴力多项式全家桶里的做法。 ### ln 同暴力多项式全家桶(等式两边同时求导)。再优化即可。 ### exp 两边同时取 ln 再移项,用牛顿迭代做。 ### 三角函数(sin、cos) 用结论(和暴力多项式全家桶里的一样(???))并优化即可。 ### P4723 【模板】常系数齐次线性递推 上课时没太听懂,下面的对这题的分析可能有误。 类比 斐波那契数列。 斐波那契数列 $f _ i = f _ {i - 1} + f _ {i - 2}$。(???) 对于 $f _ n$,把它换成 $f _ {n-1} + f_{n-2}$,然后接着往下换…… 考虑多项式 $x ^ n$ 除以 $x ^ 2 + x$。好像是这样???? 然后发现二者有一些共同之处。然后把问题转成一个多项式除以另一个多项式(可能有余)的问题。 后面记不到了。qwq 备注:等求逆板子过了记录一下经验。 2024.7.23