多项式半家桶

upd on 2025-02-08 19:33:46 星期六:优化了 exp 常数,快了一倍。

upd on 2025-02-09 14:54:01 星期日:整体优化,用了 vector 和 重载运算符,预处理了单位根,但是还没写除法,别急,现在的板子是高速状态。

upd on 2025-02-09 16:20:10 星期日:更新了除法,取模,开根。

upd on 2025-02-09 16:42:10 星期日:更新了使用指南。

upd on 2025-02-09 17:33:05 星期日:修复了一个小错误。

速度详情

多项式乘法最大点 389 ms,总时间 1.13 s

多项式乘法逆最大点 58 ms,总时间 407 ms

多项式ln最大点 82 ms,总时间 432 ms

多项式exp最大点 172 ms,总时间 851 ms

多项式快速幂最大点 229 ms,总时间 3.05s

多项式除法最大点 88 ms,总时间 385ms

多项式开根最大点 168ms,总时间 2.22s

速度还是很可观的,都用 cin/cout (关闭同步流),没有刻意卡常,基本都在最优解前几页(再前边基本都是指令集老哥了,实在比不过)。

有卷积,求逆,exp,ln,除法,取模,开根,乘方。

使用指南:

对于所有传入的多项式,必须保证是个 \(n\) 项式

如果要改模数,在 namespace 前几行有 ___P,___G,___Gi 分别是模数,原根,原根的逆元,自行更改。默认 \(10^6\),可以更改 ___LEN,注意要保证 \(\_\_\_N\)\(x+10\) 的形式。

定义一个 \(n\) 项多项式poly f(n)

  1. 卷积: 就是 *,返回a*b
  2. 求逆: 调用 invp(f,n),返回 \(f^{-1}\)
  3. 除法: 就是 /
  4. 取模: 就是 %
  5. 开根: 就是 sqrtp(f,n),返回 \(\sqrt{f}\)
  6. exp: 就是 exp(f,n)
  7. ln: 就是 ln(f,n)
  8. +,-。就是 +,-
  9. Diff 是求导,Int 是积分。
  10. Pow(x,k) 是求 \(x^k\),如果不保证 \(x[0]=1\),请调用 powp(x,to_string(k))

调用以上所有函数都需要在主函数先调用一次 Poly_pre(),调用 ln,exp,Pow 时需要先调用一次 _invpre()

代码在下面!!!

code
namespace PolyBasedNTT{
#define IL inline
  const int __LEN = 4e6 + 10;
  const int ___P = 998244353;
  const int ___G = 3,___Gi = 332748118;
  using poly = vector<int>;
  int _t[__LEN];poly __RT[22];
  int lastnnn = 0,lastlim = 0;
  poly invint;int lastinvn = 0;
  template<class T> T FMod(const T &x){return x >= ___P?x-___P:x;}
  IL void _invpre(){
    invint.resize(__LEN-9);invint[1] = 1;
    for(int i = 2;i <= __LEN-10; ++i) invint[i] = 1ll*invint[___P%i]*(___P-___P/i)%___P;
  }
  IL int Quick_power(int a,int b,int Mod = ___P){
    int res = 1;
    for(;b;b >>= 1,a = 1ll*a*a%Mod)
      if(b&1) res = 1ll*res*a%Mod;
    return res;
  }
  IL int inv_int(int a){return Quick_power(a,___P-2);}
  IL void Poly_pre(){
    __RT[0].resize(1,1);int pp = __lg(__LEN);
    for(int i = 1;i < pp; ++i){
      poly &g = __RT[i];g.resize(1<<i,1);
      const int t = Quick_power(___G,___P>>(i+1));
      for(int j = 1;j < (1<<i); ++j) g[j] =1ll*g[j-1]*t%___P;
    }
  }
  IL int _prepare(int n){
    if(lastnnn == n) return lastlim;
    lastnnn = n;int lim = 1,ct = -1;
    while(lim < n) lim <<= 1,ct++;
    for(int i = 0;i < lim; ++i) _t[i] = (_t[i>>1]>>1)|((i&1)<<ct);
    return (lastlim = lim);
  }
  IL void NTT(poly &a,int n,bool type = true){
    n = _prepare(n);a.resize(n);
    for(int i = 1;i < n; ++i) if(i < _t[i]) swap(a[i],a[_t[i]]);
    for(int mid = 1,now = 0;mid < n;mid <<= 1, ++now){
      auto &G = __RT[now];
      for(int Res = mid << 1,j = 0;j < n;j += Res){
        for(int k = 0;k < mid; ++k){
          int x = a[j+k],y = 1ll*G[k]*a[j+k+mid]%___P;
          a[j+k] = FMod(x + y);
          a[j+k+mid] = FMod(x - y + ___P);
        }
      }
    }
    if(!type) reverse(a.begin()+1,a.end());
  }
  IL void _Reverse(poly &a){reverse(a.begin(),a.end());}
  IL poly operator * (poly a,poly b){
    int lena = a.size(),lenb = b.size();
    if(lena <= 50 && lenb <= 50){
      poly res(lena+lenb-1);
      for(int i = 0;i < lena; ++i) for(int j = 0;j < lenb; ++j)
        res[i+j] = FMod(res[i+j] + 1ll*a[i]*b[j]%___P);
      return res;
    }
    int len = lena+lenb,Len = _prepare(len),llen = inv_int(Len);
    NTT(a,len);NTT(b,len);
    for(int i = 0;i < Len; ++i) a[i] = 1ll*a[i]*b[i]%___P;
    NTT(a,len,false);
    for(int i = 0;i < len; ++i) a[i] = 1ll*a[i]*llen%___P;
    a.resize(len-1);
    return a;
  }
  IL poly operator + (poly a,poly b){
    int lena = a.size(),lenb = b.size();
    int len = max(lena,lenb);
    poly res(len);
    for(int i = 0;i < lena; ++i) res[i] = FMod(res[i] + a[i]);
    for(int i = 0;i < lenb; ++i) res[i] = FMod(res[i] + b[i]);
    return res;
  }
  IL poly operator - (poly a,poly b){
    int lena = a.size(),lenb = b.size(),len = max(lena,lenb);
    poly res(len);
    for(int i = 0;i < lena; ++i) res[i] = FMod(res[i] + a[i]);
    for(int i = 0;i < lenb; ++i) res[i] = FMod(res[i] - b[i] + ___P);
    return res;
  }
  poly invp(poly x,int n){
    if(n == 1) return {inv_int(x[0])};
    poly y = invp(poly(x.begin(),x.begin()+((n+1)>>1)),(n+1)>>1);
    int len = _prepare(n<<1);
    NTT(x,n<<1);NTT(y,n<<1);
    for(int i = 0;i < len; ++i) x[i] = 1ll*y[i]*FMod(2-1ll*x[i]*y[i]%___P+___P)%___P;
    NTT(x,n<<1,false);int llen = inv_int(len);
    for(int i = 0;i < len; ++i) x[i] = 1ll*x[i]*llen%___P;
    x.resize(n);
    return x;
  }
  IL poly operator / (poly x,poly y){
    int n = x.size(),m = y.size(),len = n - m + 1;
    _Reverse(x);_Reverse(y);y.resize(len);
    poly res = x*invp(y,len);
    res.resize(len);_Reverse(res);
    return res;
  }
  IL poly operator % (poly x,poly y){
    poly res = x - (x/y)*y;
    res.resize(y.size()-1);
    return res;
  }
  IL poly Diff(poly x){
    int len = x.size() - 1;poly res(len);
    for(int i = 0;i < len; ++i) res[i] = 1ll*x[i+1]*(i+1)%___P;
    return res;
  }
  IL poly Int(poly x){
    int len = x.size() + 1;poly res(len);
    for(int i = 1;i < len; ++i) res[i] = 1ll*x[i-1]*invint[i]%___P;
    return res;
  }
  poly ln(poly x,int n){
    poly res = Int(invp(x,n)*Diff(x));
    res.resize(n);return res;
  }
  poly exp(poly x,int n){
    if(n == 1) return {1};
    poly y = exp(poly(x.begin(),x.begin()+((n+1)>>1)),(n+1)>>1);
    poly res = y*(poly{1}-ln(y,n)+x);
    res.resize(n);
    return res;
  }
  IL int BSGS(int a,int p,int n){
    unordered_map<int,int> mp;
  	int m = ceil(sqrt(p)),tmp = 1;
  	for(int i=0;i<m;i++) mp[1ll*tmp*n%p] = i+1,tmp = 1ll*tmp*a%p;
  	int tmpp = 1;
  	for(int i=0;i<=m;i++){
  		if(mp[tmpp]) return (1ll*i*m%p-mp[tmpp]+1+p)%p;
  		tmpp=1ll*tmpp*tmp%p;
  	}
  	return -1;
  }
  poly sqrtp(poly x,int n){
    if(n == 1){
      int p = BSGS(3,___P,x[0]);
      p = Quick_power(3,p/2);
      return {min(p,___P-p)};
    }
    //x.resize(n);
    poly y = sqrtp(poly(x.begin(),x.begin()+(n+1)/2),(n+1)/2);
    poly res = (y+x*invp(y,n))*poly({(___P+1)/2});res.resize(n);
    return res;
  }
  poly Pow(poly x,int k){
    int n = x.size();
    x = ln(x,n);
    for(int i = 0;i < n; ++i) x[i] = 1ll*x[i]*k%___P;
    return exp(x,n);
  }
  poly Pow(poly x,int k,int n){
    x = ln(x,n);
    for(int i = 0;i < n; ++i) x[i] = 1ll*x[i]*k%___P;
    x = exp(x,n);
    return x;
  }
  int __GET(string s,int Mod = ___P - 1){
    int res = 0;
    for(int i = 0;i < s.size(); ++i)
      res = (res*10ll + s[i]-48)%Mod;
    return res;
  }
  poly powp(poly x,string k){
    int n = x.size(),pp = -1;
    for(int i = 0;i < n; ++i) if(x[i]) {pp = i;break;}
    for(int i = 0;i < n-pp; ++i) x[i] = x[i+pp];
    int a = x[0],ia = inv_int(a);
    for(auto &i:x) i = 1ll*i*ia%___P;
    a = Quick_power(a,__GET(k));
    if(k.size() <= 9){
      int p = 0;
      for(char c:k) p = p*10+c-48;
      if(1ll*pp*p < n){
        x.resize(n-pp*p);
        x = Pow(x,p,n-pp*p);
        poly res(n);
        for(int i = 0;i < n-pp*p; ++i) res[i+pp*p] = 1ll*x[i]*a%___P;
        return res;
      }
      else return poly(n);
    }
    if(pp) return poly(n);
    x = ln(x,n) * poly{__GET(k,___P)};
    x = exp(x,n)*(poly){a};
    x.resize(n);
    return x;
  }
#undef IL
}using namespace PolyBasedNTT;

代码在上面!!!

简单说一下原理(以后就不写了)。

多项式卷积是前置知识,自己找,也可以看看我的

求逆

即求一个多项式 \(G(x)\) 使得 \(F(x)*G(x) \equiv 1\pmod{x^n}\)

考虑倍增,假如当前已经求出了 \(\bmod\;{x^{\left\lceil\frac{n}{2}\right\rceil}}\) 的逆元,设其为 \(G^{\prime}(x)\)

那么有 \(F*G^{\prime}\equiv 1\pmod {x^{\left\lceil\frac{n}{2}\right\rceil}}\)

显然 \(F*G\equiv 1\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}}\)

那么两式联立就有 \(G-G^{\prime}\equiv 0\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}}\)

两边平方,再同乘 \(F\)\(F{G^{\prime2}}-2G^{\prime}+G\equiv 0\pmod{x^n}\)

\(G\equiv 2G^{\prime}-FG^{\prime2}\pmod{x^n}\)

除法

已知 \(F(x)=\sum\limits_{i=0}^{n-1}a_ix^i\),定义 \(F^{T}(x)=\sum\limits_{i=0}^{n-1}a_{n-1-i}x^i\)

就是把系数翻转。

已知一个 \(n\) 项式 \(F(x)\)\(m\) 项式 \(G(x)\),求一个 \(n-m+1\) 项式 \(Q(x)\),一个项数为 \(m-2\) 的多项式 \(R(x)\)(不足用 \(0\) 补齐),使得 \(F(x)=Q(x)*G(x)+R(x)\)

考虑如何删掉余项变成多项式求逆。一般通过 \(\bmod\;x^n\) 除去 \(\ge x^n\) 的项,但是这里 \(R\) 的项数很小,无法删去。先不管这些,直接暴力推式子。

\[\begin{aligned} F(x)&=Q(x)*G(x)+R(x)\\ F(x^{-1})&=Q(x^{-1})*G(x^{-1})+R(x^{-1})\\ x^nF(x^{-1})&=x^nQ(x^{-1})*G(x^{-1})+x^nR(x^{-1}) \end{aligned}\]

这时候,巧妙地应用刚刚定义的那个操作,就有。

\[\begin{aligned} F^T(x)&=Q^T(x)*G^T(x)+x^{n-m+1}R^T(x)\\ \end{aligned}\]

然后两边同时 \(\bmod\;x^{n-m+1}\),就有

\[F^T(x)=Q^T(x)*G^T(x)\pmod{x^{n-m+1}} \]

然后求出 \(Q,R\) 即可。

平方根

前置知识:牛顿迭代,简单求导,积分。

可以看看我的牛顿迭代笔记喵?

求导只需要知道 \(y=x^a,y^{\prime}=ax^{a-1}\),还有 \((f(x)+g(x))^{\prime}=f^{\prime}(x)+g^{\prime}(x)\)

积分只需要知道 \(\int x^a\mathrm{d}x=\frac{x^{a+1}}{a+1}+C\),其中 \(C\) 是常数。

\(G(Q(x))=Q^2(x)-F(x)\),即求 \(G(x)\) 的零点。

考虑倍增法,假设已经求出了 \({Q_*}^2(x)\equiv F(x)\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}}\)

直接大力上牛顿迭代,就有 \(Q(x)=Q_*(x)-\frac{G(Q_*(x))}{G^{\prime}(Q_*(x))}\)

拆一下就是 \(Q(x)=Q_*(x)-\frac{Q_*^2(x)-F(x)}{2Q_*(x)}\)

直接卷积求逆跑就行。

多项式 ln

复合函数求导链式法则:\(f(g(x))=f^{\prime}(g(x))g^{\prime}(x)\)

\(Q(x)=ln(x)\),直接推式子:

\[\begin{aligned} G(x)&=Q(F(x))\\ G^{\prime}(x)&=\frac{1}{F(x)}F^{\prime}(x)\\ \end{aligned}\]

就是对 \(F(x)\) 求导,求逆,卷积,求出 \(G^{\prime}\),然后再对 \(G^{\prime}\) 积分即可。

多项式 exp

即求 \(G(x)=e^{F(x)}\),对两边取 \(ln\)\(ln(G(x))=F(x)\)

\(Q(x)=ln(x)-F\),考虑牛顿迭代+倍增。

\(G(x)=G_*(x)-\frac{Q(G_*(x))}{Q^{\prime}(G_*(x))}\)

就是 \(G(x)=G_*(x)-\frac{ln(G_*(x))-F(x)}{G_*^{-1}(x)}\)

然后就有 \(G(x)=G_*(x)(1-ln(G_*(x))-F(x))\)

倍增求即可。

posted @ 2025-02-07 21:40  CuFeO4  阅读(108)  评论(10)    收藏  举报