多项式学习笔记

在最前面讲一些废话

Q:你不是都退役了吗,为什么还更博客?

A:文化课太无聊了,我来更新更新。

Q:不是说不想学多项式了吗,为什么还是学了?

A:下一周有浙大的校赛,然后发现我们队里没有人会多项式,他们都去吃鸡了,于是就(把锅甩给我了)去学了。

Q:为什么是准备浙大校赛,而不是准备ZJOIday2。

A:因为我day1被出题人卡常数送退役了,day2也没啥意义,而且出多项式我也一定拿不了什么分。

Q:那你这么菜这篇文章有什么好看的。

A:因为我数学和记性不太好,写下来帮助自己理解用的,想学多项式左转 txc好哥哥的博客


多项式乘法

都9102年了还有人不会 \(\text{FFT/NTT}\) 吗,想必任意模数也没啥用,就省略好了。

算了,还是贴一个跑的非常慢的代码吧。

code

inline void timesinit(int lenth){
    for(len = 1, lg = 0; len <= lenth; len <<= 1, lg++);
    for(int i = 0; i < len; i++)
       rev[i] = (rev[i>>1] >> 1) | ((i & 1) << (lg - 1));
}
inline void DFT(int *a, int sgn){
   for(int i = 0; i < len; i++) if(i < rev[i]) swap(a[i], a[rev[i]]);
   for(int k = 2; k <= len; k <<= 1){
       int w = ~sgn ? Pow(G, (P - 1) / k) : Pow(Pow(G, (P - 1) / k), P - 2);
       for(int i = 0; i < len; i += k){
           int now = 1;
           for(int j = i; j < i + (k >> 1); j++, now = 1ll * now * w % P){
               int x = a[j], y = 1ll * a[j+(k>>1)] * now % P;
               a[j] = (x + y) % P, a[j+(k>>1)] = (x - y + P) % P;
           }
      }
  }
  if(sgn == -1){
      int Inv = Pow(len, P - 2);
      for(int i = 0; i < len; i++) a[i] = 1ll * a[i] * Inv % P;
  }
}
inline void times(int *a, int *b, int *c){
    for(int i = 0; i < len; i++) c[i] = 1ll * a[i] * b[i] % P;
}


多项式求逆

现在有多项式 \(F(x)\) ,求一个多项式 \(G(x)\) ,满足 \(F(x)G(x)\equiv 1\)

考虑倍增,假设当前要求
\[ F(x)G(x)\equiv1(\bmod x^n) \]
并且已知
\[ F(x)G_0(x)\equiv1(\bmod x^\frac{n}{2}) \]
两式相减可以得到
\[ F(x)(G(x)-G_0(x))\equiv0(\bmod x^\frac{n}{2}) \\ G(x)-G_0(x)\equiv0(\bmod x^\frac{n}{2}) \\ \]
两边都平方可以得到
\[ G^2(x)-2G(x)G_0(x)+G_0^2(x)\equiv 0(\bmod x^n) \]
乘上 \(F(x)\) 得到
\[ G(x)-2G_0(x)+G_0^2(x)F(x)\equiv0(\bmod x^n) \\ G(x)\equiv G_0(x)(2-G_0(x)F(x)) (\bmod x^n) \]
一次操作需要两次 \(\text{DFT}\) ,复杂度 \(T(n)=T(\frac{n}{2})+\mathcal O(n\log n)=\mathcal O(n\log n)\)

code

inline void getinv(int *a, int *b, int n){
    if(n == 1) return (void) (b[0] = Pow(a[0], P - 2));
    getinv(a, b, (n + 1) / 2);
    static int tmp[N];
    timesinit(n * 2 - 1);
    for(int i = 0; i < len; i++) tmp[i] = i < n ? a[i] : 0;
    DFT(tmp, 1), DFT(b, 1);
    for(int i = 0; i < len; i++) 
        b[i] = 1ll * (2 - 1ll * tmp[i] * b[i] % P + P) % P * b[i] % P;
    DFT(b, -1);
    for(int i = n; i < len; i++) b[i] = 0;
    for(int i = 0; i < len; i++) tmp[i] = 0;
}


多项式牛顿迭代

和普通的牛顿迭代形式一样,从多项式的角度推一下

我们有关于多项式 \(F(x)\) 的方程 \(G(F(x))=0\),并且假设当前已经求出
\[ G(F_0(x))\equiv0\ (\bmod x^{n}) \]
在这基础上我们要求 \(G(F(x))\equiv 0(\bmod x^{2n})\)

\(G(F(x))\)\(F_0(x)\) 处泰勒展开,可以得到
\[ G(F(x))=\sum_{i}\dfrac{G^i(F_0(x))}{i!}(F(x)-F_0(x))^i \]
其中 \(G^i\) 表示 \(G\)\(i\) 阶导函数。

由于 \(F(x)-F_0(x)\) 的前 \(n\) 项系数都是 \(0\) ,当 \((F(x)-F_0(x))^i\) 的次数大于 \(1\) 时,所得多项式的系数都是 \(0\) ,因为卷积的时候每一次乘法都至少有一个系数为 \(0\) 的项参与,所以可以得到
\[ G(F(x))\equiv G(F_0(x))+G'(F_0(x))(F(x)-F_0(x))\equiv 0\ (\bmod x^{2n}) \\ F(x)\equiv F_0(x)+\dfrac{G(F(x))-G(F_0(x))}{G'(F_0(x))} (\bmod x^{2n}) \]
因为 \(G(F(x))\equiv 0 (\bmod x^{2n})\),所以有
\[ F(x)\equiv F_0(x)-\dfrac{G(F_0(x))}{G'(F_0(x))} (\bmod x^{2n}) \]
可以用来推很多东西,开根,exp,三角函数什么的,好像不能用来推求逆,推出来的式子要用到求逆。


多项式开根

现在有一个多项式 \(F(x)\),要求多项式 \(G(x)\equiv \sqrt{F(x)}\ (\bmod x^m)\)


\[ G(x)^2 =F(x)\\G(x)^2-F(x)=0 \]
假设当前已经有 \(G_0(x)^2-F(x)\equiv0\ (\bmod x^n)\) ,要求 \[G(x)^2-F(x)\equiv0\ (\bmod x^{2n})\]

套用牛顿迭代
\[ G(x)\equiv G_0(x)-\dfrac{G_0(x)^2-F(x)}{2G_0(x)}\ (\bmod x^{2n}) \\ G(x)\equiv \dfrac{1}{2}\left( G_0(x)-\dfrac{F(x)}{G_0(x)}) \right) \]
需要保证 \(F(x)\) 的常数项有二次剩余。

复杂度根据主定理 \(T(n)=T(\dfrac{n}{2})+\mathcal O(n\log n)=\mathcal O(n \log n)\) ,每次需要一遍乘法一遍求逆 。

code

inline void getsqrt(int *a, int *b, int n){
    static int tmp1[N], tmp2[N];
    if(n == 1) return (void) (b[0] = 1);
    getsqrt(a, b, (n + 1) / 2);
    for(int i = 0; i < n; i++) tmp1[i] = a[i];
    getinv(b, tmp2, n), timesinit(n * 2 - 1);
    DFT(tmp1, 1), DFT(tmp2, 1);
    for(int i = 0; i < len; i++) tmp1[i] = 1ll * tmp1[i] * tmp2[i] % P;
    DFT(tmp1, -1);
    for(int i = 0; i < len; i++) 
        b[i] = 1ll * (b[i] + tmp1[i]) % P * Pow(2, P - 2) % P; 
    for(int i = n; i < len; i++) b[i] = 0;
    for(int i = 0; i < len; i++) tmp1[i] = tmp2[i] = 0;
}


多项式求ln

现在有一个多项式 \(F(x)\) ,要求 \(\ln(F(x))\)
\[ \ln(F(x))=\int (\ln(F(x)))' \\ =\int (\ln'(F(x))\cdot F'(x))\\ =\int\dfrac{F'(x)}{F(x)} \]

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

多项式求逆复杂度 \(\mathcal O(n\log n)\),求导和积分下面式子来就好了,都是其中较为简单的。
\[ F(x)=\sum_{i\geq0}A_ix^i \\ F'(x)=\sum_{i\geq0}iA_{i+1}x^i \]
所以总的复杂度也是 \(\mathcal O(n\log n)\),注意积分以后是 \(F(x)+c\) 的形式,要保证 \(F(x)\) 常数项为 \(1\)

code

inline void getln(int *a, int *b, int n){
    static int tmp[N];
    getinv(a, b, n), timesinit(n * 2 - 1);
    for(int i = 1; i < n; i++) tmp[i-1] = 1ll * a[i] * i % P;
    for(int i = n - 1; i < len; i++) tmp[i] = 0;
    DFT(tmp, 1), DFT(b, 1);
    for(int i = 0; i < len; i++) b[i] = 1ll * tmp[i] * b[i] % P;
    DFT(b, -1);
    for(int i = len - 1; i; i--) b[i] = 1ll * b[i-1] * Pow(i, P - 2) % P;
    b[0] = 0;   
    for(int i = n; i < len; i++) b[i] = 0;
    for(int i = 0; i < len; i++) tmp[i] = 0;
}


多项式求exp

现在有一个多项式 \(F(x)\) ,要求 \(G(x)\equiv e^{F(x)}\ (\bmod x^m)\)

先取一下对数
\[ \ln(G(x))-F(x)=0 \]
假设当前已经有 \(\ln(G_0(x))-F(x)\equiv0\ (\bmod x^n)\) ,要求 \[\ln(G_0(x))-F(x)\equiv0\ (\bmod x^{2n})\]

直接套用牛顿迭代
\[ G(x)\equiv G_0(x)- G_0(x)(\ln(G_0(x))-F(x))\ (\bmod x^{2n})\\ G(x)\equiv G_0(x)(1-\ln(G_0(x))+F(x))\ (\bmod x^{2n}) \]
需要保证 \(F(x)\) 的常数项为 \(0\) ,求出来的 \(G(x)\) 的常数项一定为 \(1\)

复杂度根据主定理 \(T(n)=T(\dfrac{n}{2})+\mathcal O(n\log n)=\mathcal O(n \log n)\) ,每次需要一遍乘法一遍 \(\ln\)

code

inline void getexp(int *a, int *b, int n){
    static int tmp[N];
    if(n == 1) return (void) (b[0] = 1);
    getexp(a, b, (n + 1) / 2);
    getln(b, tmp, n), timesinit(n * 2 - 1);
    for(int i = 0; i < n; i++) tmp[i] = (!i - tmp[i] + a[i] + P) % P;
    DFT(tmp, 1), DFT(b, 1);
    for(int i = 0; i < len; i++) b[i] = 1ll * b[i] * tmp[i] % P;
    DFT(b, -1);
    for(int i = n; i < len; i++) b[i] = 0;
    for(int i = 0; i < len; i++) tmp[i] = 0;
}


多项式幂函数

现在有一个多项式 \(F(x)\) ,要求 \(G(x)\equiv F(x)^k (\bmod x^m)\)

如果 \(F(x)\) 的常数项为 \(1\) ,那么有
\[ F(x)^k=\exp(k\ln(F(x))) \]
否则需要把 \(F(x)\) 的常数项变成 \(1\) ,找到最低的不为 \(0\) 的项的系数 \([x^p]\) ,先让多项式除以 \([x^p]x^p\) 这个单项式,然后就变成了一个常数项为 \(1\) 的多项式 \(F_0(x)\)

最后再把这个单项式乘回来,有
\[ F(x)=[x^p]^kx^{pk}F_0(x) \]
需要一遍 \(\ln\) 和一遍 \(\exp\) ,复杂度 \(\mathcal O(n\log n)\) ,需要判的细节略微有点多。

code

inline void power(int *a, int *b, int n, int m, ll k){
    static int tmp[N];
    for(int i = 0; i < m; i++) b[i] = 0;
    int fir = -1;
    for(int i = 0; i < n; i++) if(a[i]){ fir = i; break; }
    if(fir && k >= m) return;
    if(fir == -1 || 1ll * fir * k >= m) return;
    for(int i = fir; i < n; i++) b[i-fir] = a[i];
    for(int i = 0; i < n - fir; i++) 
        b[i] = 1ll * b[i] * Pow(a[fir], P - 2) % P;
    getln(b, tmp, m);
    for(int i = 0; i < m; i++) 
        b[i] = 1ll * tmp[i] * (k % P) % P, tmp[i] = 0;
    getexp(b, tmp, m);
    for(int i = m; i >= fir * k; i--) 
        b[i] = 1ll * tmp[i-fir*k] * Pow(a[fir], k % (P - 1)) % P;
    for(int i = 0; i < fir * k; i++) b[i] = 0;
    for(int i = 0; i < m; i++) tmp[i] = 0;
}
posted @ 2019-04-08 18:40 Joyemang33 阅读(...) 评论(...) 编辑 收藏