【数值方法&多项式】

二分和三分

二分

  • 单调性

(1) 单调性:

  • 左边成立,右边不成立。
  • 在成立者之中,找到最靠右的位置。其实就是找到分界点。
int l=1,r=n,res=-1;
while(l<=r){
    int m=(l+r)>>1;
    if(check(m)) res=m,l=m+1;
    else r=m-1;
}

(2) 单调性:左边不成立,右边成立。

  • 在成立者之中,找到最靠左的位置。
  • 往外一格就是upper_bound,但这个不是内置lower_bound的位置(内置lower_bound指向第一个成立者)!
int l=1,r=n,res=-1;
while(l<=r){
    int m=(l+r)>>1;
    if(check(m)) res=m,r=m-1;
    l=m+1;
}

(3) 单调性:中间某位置成立。左边过小,右边过大。

  • 找中间某位置。
int l=1,r=n,res=-1;
while(l<=r){
    int m=(l+r)>>1;
    if(a[m]<5) l=m+1;
    else if(a[m]>5) r=m-1;
    else {res=m;break;}
}

(4) 数组从0开始,单调性:左边成立,右边不成立。

  • 找最靠右的成立者。
int l=-1,r=n;
    while(r-l>1){
        int m=(l+r)>>1;
        if(check(m)) l=m;
        else r=m;
    }

(5) 数组从0开始,单调性:左边过小,右边过大。中间成立。

  • 找中间位置
int l=-1,r=n,res=-1;
while(r-l>1){
      int m=(l+r)>>1;
      if(a[m]<5) l=m;
      else if(a[m]>5) r=m;
      else {res=m;break;}
  }

多项式

前置知识

数论、群论、复数

拉格朗日插值

有限差分法

  • 约束:步长为1
  • 时间复杂度 O ( n 2 ) O(n^2) O(n2)

每一次对一个n阶多项式做差分,相当于将它求导,经过n次差分之后,我们会得到一组定值。此时,等于 n ! × a n n!\times a_n n!×an。因此我们可以得出 a n a_n an的值。现在,我们把每一对都减去这个 a n a_n an的贡献;再算 n − 1 ! × a n − 1 {n-1}!\times a_{n-1} n1!×an1。。。就能把所有的系数求出来。

拉格朗日插值

  • 构造
  • 时间复杂度 O ( n 2 ) O(n^2) O(n2)

在这里插入图片描述

洛谷的板子

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll mod = 998244353;
const int maxn = 2e3+4;
ll x[maxn],y[maxn];

ll quick_p(ll a,ll n){
    a%=mod;
    ll ans = 1;
    while(n){
        if(n&1) ans = ans*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return ans;
}

int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i){
        scanf("%d%d",&x[i],&y[i]);
    }
    ll ans = 0;
    for(int i=1;i<=n;++i){
        ll s1=1,s2=1;
        for(int j=1;j<=n;++j){
            if(i==j) continue;
            s1 = s1*(k-x[j])%mod;
            s2 = s2*(x[i]-x[j])%mod;
        }
        ans += y[i]*s1*quick_p(s2,mod-2)%mod;
        ans%=mod;
    }
    cout<<(ans+mod)%mod<<endl;
}

references

OI wiki 拉格朗日插值
有限差分法

快速傅里叶变换(FFT)

  • 时间复杂度 O ( n l g n ) O(nlgn) O(nlgn)
  • 没什么局限的算法
  • 蝴蝶变换(位逆序变换)
  • DFT和IDFT过程
  • 递归
  • 单位复根的特性
  • Vandermonde矩阵的逆矩阵

P3803 【模板】多项式乘法(FFT)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;

const double PI = acos(-1.0);
struct Complex {
    double x, y;
    Complex(double _x = 0.0, double _y = 0.0) {
        x = _x;
        y = _y;
    }
    Complex operator-(const Complex &b) const {
        return Complex(x - b.x, y - b.y);
    }
    Complex operator+(const Complex &b) const {
        return Complex(x + b.x, y + b.y);
    }
    Complex operator*(const Complex &b) const {
        return Complex(x * b.x - y * b.y,x * b.y + y * b.x);
    }
};

int rev[maxn<<2];
// O(n) 位逆序置换
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]]);
}

// 非递归fft
void fft(Complex y[], int len, int on) {
    change(y, len);
    for (int h = 2; h <= len; h <<= 1) {
        Complex wn(cos(2 * PI / h), sin(on * 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];
                Complex t = w * y[k + h / 2];
                y[k] = u + t;
                y[k + h / 2] = u - t;
                w = w * wn;
            }
        }
    }
    if (on == -1)
        for (int i = 0; i < len; ++i) y[i].x /= len;
}

Complex f[maxn<<2], g[maxn<<2];

int main() {
    int n,m;scanf("%d%d",&n,&m);
    int len = 1;
    while (len < (n+1) * 2 || len < (m+1) * 2) len <<= 1;
    for (int i = 0; i < n + 1; ++i) {
        int tmp;
        scanf("%d",&tmp);
        f[i] = Complex(tmp,0);
    }
    for (int i=n+1;i<len;++i) f[i] = Complex(0,0);
    for (int i=0;i<m+1;++i) {
        int tmp;
        scanf("%d",&tmp);
        g[i] = Complex(tmp,0);
    }
    for (int i=m+1;i<len;++i) g[i] = Complex(0,0);
    fft(f,len,1); fft(g,len,1);
    for (int i=0;i<len;++i) f[i] = f[i]*g[i];
    fft(f,len,-1);
    for (int i=0; i<n+m+1; ++i)
        cout<<int(f[i].x+0.5)<<" ";
}

洛谷3338 力
纪念写的第一道fft。
在这里插入图片描述
首先
F j = ∑ i = 1 j − 1 q i ( i − j ) 2 − ∑ i = j + 1 n q i ( i − j ) 2 F_j = \sum_{i=1}^{j-1}\frac{q_i}{(i-j)^2}-\sum_{i=j+1}^{n}\frac{q_i}{(i-j)^2} Fj=i=1j1(ij)2qii=j+1n(ij)2qi
加多一项,前减去一个后加上一个,不变化
F j = ∑ i = 1 j q i ( i − j ) 2 − ∑ i = j n q i ( i − j ) 2 F_j =\sum_{i=1}^j\frac{q_i}{(i-j)^2}-\sum_{i=j}^n\frac{q_i}{(i-j)^2} Fj=i=1j(ij)2qii=jn(ij)2qi
设两个函数。设他们下标为0的情况,扩展为0到j。然后可得前一个部分是一个卷积。
后一个也写成这两个函数的样子。然后把枚举顺序从j到n改成从0到n-j。把i也改成i+j。
得到新的公式。把f倒转过来。得卷积。
然后答案就得到了,带进去算即可。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;

const double PI = acos(-1.0);
struct Complex {
    double x, y;
    Complex(double _x = 0.0, double _y = 0.0) {
        x = _x;
        y = _y;
    }
    Complex operator-(const Complex &b) const {
        return Complex(x - b.x, y - b.y);
    }
    Complex operator+(const Complex &b) const {
        return Complex(x + b.x, y + b.y);
    }
    Complex operator*(const Complex &b) const {
        return Complex(x * b.x - y * b.y,x * b.y + y * b.x);
    }
};

int rev[maxn<<2];
// O(n) 位逆序置换
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]]);
}

// 非递归fft
void fft(Complex y[], int len, int on) {
    change(y, len);
    for (int h = 2; h <= len; h <<= 1) {
        Complex wn(cos(2 * PI / h), sin(on * 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];
                Complex t = w * y[k + h / 2];
                y[k] = u + t;
                y[k + h / 2] = u - t;
                w = w * wn;
            }
        }
    }
    if (on == -1)
        for (int i = 0; i < len; ++i) y[i].x /= len;
}

Complex f[maxn<<2], g[maxn<<2], f2[maxn<<2];

double q[maxn];
int main() {
    int n;scanf("%d",&n);

    f[0] = Complex(0,0);
    g[0] = Complex(0,0);
    f2[0] = Complex(0,0);
    // 长度为n+1。0~n。
    for(int i=1;i<=n;++i){
        scanf("%lf",&q[i]);
        f[i] = Complex(q[i],0);
        g[i] = Complex((double)1/i/i,0);
        f2[n-i]=Complex(q[i],0);
    }

    int len = 1;
    while (len < (n+1) * 2 ) len <<= 1;
    for (int i=n+1;i<len;++i) {
        f[i] = Complex(0, 0);
        g[i] = Complex(0,0);
        f2[i] = Complex(0,0);
    }
    fft(f,len,1); fft(g,len,1);
    fft(f2,len,1);
    for (int i=0;i<len;++i) {
        f[i] = f[i]*g[i];
        f2[i] = f2[i]*g[i];
    }
    fft(f,len,-1);
    fft(f2,len,-1);
    for (int i=1; i<n+1; ++i)
        printf("%.3lf\n",f[i].x-f2[n-i].x);
}


NTT

洛谷4245 【模板】任意模数多项式乘法

为了学NTT来看的这道题。但是任意模数乘法现在好像都不流行用NTT了,都流行用FFT优化(MTT?)。但不管怎么说,NTT还是要学。
NTT的题目就是限定在了模域之下。此时我们再用FFT来写会发现精度不够(而且会爆)。天才数学家们发现了单位复根的那些特殊性质,模域下的原根也有。NTT就是原根版本的FFT。
原根与单位复根的性质对照
但是我们发现NTT只能用于特殊的模数如998244353.这有很大的限制。
如果模数为任意,怎么办?
我们这时可以考虑中国剩余定理,用三个很大(乘积大于最大的值,故不存在取模)满足NTT要求的质数来算,然后合并结果。
但是CRT直接算会爆ll。毕竟这三个p很大。我们就分步来算。
任意模数NTT介绍

#include<bits/stdc++.h>
using namespace std;
int mod;
namespace Math {
    // 一种神奇的快速幂
    inline int pw(int base,int p,const int mod){
        static int res;
        for(res=1;p;p>>=1,base=static_cast<long long> (base)*base%mod) if(p&1) res = static_cast<long long> (res)*base%mod;
        return res;
    }
    inline int inv(int x,const int mod) {return pw(x,mod-2,mod);}
}

const int mod1 = 998244353, mod2 = 1004535809, mod3 = 469762049, G = 3;// 原根均为3
const long long mod_1_2 = static_cast<long long>(mod1)*mod2;
const int inv_1 = Math::inv(mod1,mod2),inv_2 = Math::inv(mod_1_2%mod3,mod3); // 两个逆元求出

struct Int {
    int A,B,C;
    // 三个模意义下的值。新建结构体,重载运算。
    explicit inline Int() {}
    explicit inline Int(int __num) :A(__num),B(__num),C(__num){}
    explicit inline Int(int __A,int __B,int __C):A(__A),B(__B),C(__C){}
    static inline Int reduce(const Int &x){
        return Int(x.A+(x.A>>31&mod1),x.B+(x.B>>31&mod2),x.C+(x.C>>31&mod3));
    }
    inline friend Int operator + (const Int &lhs,const Int &rhs){
        return reduce(Int(lhs.A+rhs.A-mod1,lhs.B+rhs.B-mod2,lhs.C+rhs.C-mod3));
    }
    inline friend Int operator - (const Int &lhs,const Int &rhs){
        return reduce(Int(lhs.A-rhs.A,lhs.B-rhs.B,lhs.C-rhs.C));
    }
    inline friend Int operator * (const Int &lhs,const Int &rhs){
        return Int(static_cast<long long>(lhs.A)*rhs.A%mod1,static_cast<long long>(lhs.B)*rhs.B%mod2,static_cast<long long>(lhs.C)*rhs.C%mod3);
    }
    inline int get(){
        long long x = static_cast<long long>(B-A+mod2)%mod2*inv_1%mod2*mod1+A;
        return (static_cast<long long> (C-x%mod3+mod3)%mod3*inv_2%mod3*(mod_1_2%mod)%mod+x)%mod;
    }
};

#define maxn 140004  // 不知道为什么要开这么大

namespace Poly{
#define N (maxn<<1)
    int lim,s,rev[N];
    Int Wn[N|1];
    inline void init(int n){
        s=-1,lim=1;while(lim<n)lim<<=1,++s;
        for(register int i=1;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<s; // 位逆序
        const Int t(Math::pw(G,(mod1-1)/lim,mod1),Math::pw(G,(mod2-1)/lim,mod2),Math::pw(G,(mod3-1)/lim,mod3));
        *Wn = Int(1); for(register Int *i=Wn;i!=Wn+lim;++i) *(i+1)=*i*t; // 计算原版和逆元版(单位复根)
    }
    inline void NTT(Int *A,const int op=1){
        for(register int i=1;i<lim;++i) if(i<rev[i]) std::swap(A[i],A[rev[i]]);
        for(register int mid=1;mid<lim;mid<<=1){
            const int t=lim/mid>>1;
            for(register int i=0;i<lim;i+=mid<<1){
                for(register int j=0;j<mid;++j){
                    const Int W = op?Wn[t*j]:Wn[lim-t*j];
                    const Int X = A[i+j],Y = A[i+j+mid]*W;
                    A[i+j]=X+Y,A[i+j+mid]=X-Y;
                }
            }
        }
        if(!op){
            const Int ilim(Math::inv(lim,mod1),Math::inv(lim,mod2),Math::inv(lim,mod3));
            for(register Int *i=A;i!=A+lim;++i) *i=(*i)*ilim;
        }
    }
#undef N
}

int n,m;
Int A[maxn<<1],B[maxn<<1];
int main(){
    scanf("%d%d%d",&n,&m,&mod);++n,++m;
    for(int i=0,x;i<n;++i) scanf("%d",&x),A[i]=Int(x%mod);
    for(int i=0,x;i<m;++i) scanf("%d",&x),B[i]=Int(x%mod);
    Poly::init(n+m);
    Poly::NTT(A),Poly::NTT(B);
    for(int i=0;i<Poly::lim;++i) A[i] = A[i]*B[i];
    Poly::NTT(A,0);
    for(int i=0;i<n+m-1;++i){
        printf("%d",A[i].get());
        putchar(i==n+m-2?'\n':' ');
    }
}

高精度

POJ1737 Connected Graph有标号简单无向连通图计数
每个点是不一样的。
这道题可以用指数生成函数EGF来写,貌似需要多项式求逆之类的。。因为是高精。
但也可以考虑公式递推。
此处的排列组合数如何计算呢?
为了防止重复计数或遗漏,我们考虑只关注n个点的其中某个点,let’s say it is A的情况。仅对于该点的情况进行分类讨论。
我们把问题:寻找无向连通图的种类数。转化为任意图的种类减去不连通的图的种类数。
那么对于该点,必然属于一个大小从1到n-1(设为k)的连通块。这个连通块内的其他点是在n-1个中选择k-1个。这个连通块之外的点是任意图。
那么设f为无向连通图个数,h为任意图个数,g为不连通的图个数,得到公式
f ( n ) = h ( n ) − g ( n ) f(n) = h(n)-g(n) f(n)=h(n)g(n)
h ( n ) = 2 ( n − 1 ) n 2 h(n)=2^{\frac{(n-1)n}{2}} h(n)=22(n1)n
g ( n ) = ∑ i = 1 n − 1 ( n − 1 i − 1 ) f ( i ) h ( n − i ) g(n)=\sum_{i=1}^{n-1}\tbinom{n-1}{i-1}f(i)h(n-i) g(n)=i=1n1(i1n1)f(i)h(ni)
显然2^250次幂会爆炸大,我们要把该递推式化简。

下面板子是一种vector写法,在使用上述公式处理的话会出现RE。问题在赋值函数处。
所以使用了一个生成函数的公式,即
f ( n ) = ∑ i = 1 n − 1 ( n − 2 i − 1 ) f ( i ) f ( n − i ) ( 2 i − 1 ) f(n)=\sum_{i=1}^{n-1}\tbinom{n-2}{i-1}f(i)f(n-i)(2^{i}-1) f(n)=i=1n1(i1n2)f(i)f(ni)(2i1)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>

typedef long long ll;
const int maxn = 400;
int n;
struct BigInt{
    std::vector<ll> v;

    BigInt(){*this = 0;}
    BigInt(int x){*this = x;}

    BigInt &operator=(int x){
        v.clear();
        do v.push_back(x%10);while(x/=10);
        return *this;
    }

    BigInt &operator=(const BigInt &x){
        v.resize(x.v.size());
        memcpy(const_cast<ll *>(v.data()),x.v.data(),x.v.size()*sizeof(ll));
        return *this;
    }

    friend BigInt operator+(const BigInt &a,const BigInt &b){
        BigInt result;
        result.v.clear();
        bool flag = false;
        for(int i=0;i<(int)std::max(a.v.size(),b.v.size());++i){
            int tmp = 0;
            if(i<(int)a.v.size()) tmp+=a.v[i];
            if(i<(int)b.v.size()) tmp+=b.v[i];
            if(flag) tmp++,flag=false;
            if(tmp>=10) tmp-=10,flag=true;
            result.v.push_back(tmp);
        }
        if(flag) result.v.push_back(1);
        return result;
    }

    friend BigInt &operator+=(BigInt &a,const BigInt &b){
        return a = a+b;
    }

    friend BigInt operator-(const BigInt &a,const BigInt &b){
        BigInt result;
        result.v.clear();
        bool flag = false;
        for(int i=0;i<(int)a.v.size();++i){
            int tmp = a.v[i];
            if(i<(int)b.v.size()) tmp -= b.v[i];
            if(flag) tmp--,flag = false;
            if(tmp<0) tmp+=10,flag=true;
            result.v.push_back(tmp);
        }

        int size = result.v.size();
        while(size>1&&result.v[size-1]==0) size--;
        result.v.resize(size);

        return result;
    }

    friend BigInt &operator-=(BigInt &a,const BigInt &b){
        return a = a-b;
    }

    friend BigInt operator*(const BigInt &a,const BigInt &b){
        BigInt temp,result;
        temp.v.clear();
        temp.v.resize((int)a.v.size()+(int)b.v.size()-1);
        result.v.clear();
        int flag = 0;
        for(int i=0;i<(int)a.v.size();++i)
            for(int j=0;j<(int)b.v.size();++j)
                temp.v[i+j] += a.v[i]*b.v[j];
        for(int i=0;i<(int)temp.v.size();++i){
            int tmp = temp.v[i] + flag;
            flag = tmp/10;
            result.v.push_back(tmp%10);
        }
        while(flag){
            result.v.push_back(flag%10);
            flag /= 10;
        }
        return result;
    }

    friend BigInt operator*(const BigInt &a,int &b){
        BigInt temp,result;
        result.v.clear();
        temp.v.clear();
        int flag = 0;
        for(int i=0;i<(int)a.v.size();++i) temp.v.push_back(a.v[i]*b);
        for(int i=0;i<(int)temp.v.size();++i) {
            int tmp = temp.v[i];
            tmp += flag;
            flag = tmp/10;
            result.v.push_back(tmp%10);
        }
        while(flag){
            result.v.push_back(flag%10);
            flag /= 10;
        }
        return result;
    }
};

// 重载输出big integer
std::ostream &operator<<(std::ostream &out, const BigInt &x){
    for(int i=x.v.size()-1;i>=0;--i) out << (char)(x.v[i]+'0');
    return out;
}

const int MAXN = 50;

BigInt combo[MAXN+1][MAXN+1];
BigInt bin[MAXN+1];

inline void makeComboTable(){
    for(int i=1;i<=MAXN;++i){
        combo[i][0] = combo[i][i] = 1;
        for(int j=1;j<i;++j){
            combo[i][j] = combo[i-1][j] + combo[i-1][j-1];
        }
    }
}

inline BigInt &C(int n,int k){
    return combo[n][k];
}

inline void makePowerTwoTable(){
    bin[0] = 1;
    for(int i=1;i<=MAXN;++i) bin[i] = bin[i-1]*2;
}

BigInt f[MAXN+1];

int main(){
    makeComboTable();
    makePowerTwoTable();
    f[1] = 1;f[2] = 1;
    for(int i=3;i<=MAXN;++i){
        for(int j=1;j<i;++j){
            f[i] += C(i-2,j-1)*f[i-j]*f[j]*(bin[j]-1);
        }
    }
    int n;
    while(~scanf("%d",&n),n){
        std::cout<<f[n]<<std::endl;
    }
}

还是整点阳间的板子吧(数组模拟)

#include <string>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef int ll;
inline ll read(){
    ll s=0; bool f=0; char ch=' ';
    while(!isdigit(ch)) {f|=(ch=='-'); ch=getchar();}
    while(isdigit(ch)) {s=(s<<3)+(s<<1)+(ch^48); ch=getchar();}
    return (f)?(-s):(s);
}
#define R(x) x=read()
inline void write(ll x){
    if(x<0) {putchar('-'); x=-x;}
    if(x<10) {putchar(x+'0'); return;}
    write(x/10); putchar((x%10)+'0');
}
#define W(x) write(x),putchar(' ')
#define Wl(x) write(x),putchar('\n')
const int power=4,Base=10000;
struct Int{
    int a[205];
    Int() {memset(a,0,sizeof a);}
    Int(int x){
        memset(a,0,sizeof a);
        while(x>0){
            a[++a[0]]=x%Base; x/=Base;
        }
    }
    inline void print(){
        int i;
        write(a[a[0]]);
        for(i=a[0]-1;i>=1;i--){
            if(a[i]<1000) putchar('0');
            if(a[i]<100) putchar('0');
            if(a[i]<10) putchar('0');
            write(a[i]);
        }
    }
}Bin[2005],C[55][55],Ans[55];
#define Pl(x) x.print(),putchar('\n')
inline Int operator+(Int p,Int q)
{
    int i; Int res=p; res.a[0]=max(p.a[0],q.a[0]);
    for(i=1;i<=q.a[0];i++){
        res.a[i]+=q.a[i];
        res.a[i+1]+=res.a[i]/Base;
        res.a[i]%=Base;
    }
    while(res.a[res.a[0]+1]) res.a[0]++;
    return res;
}
inline Int operator-(Int p,Int q){
    int i; Int res=p;
    for(i=1;i<=q.a[0];i++){
        res.a[i]-=q.a[i];
        if(res.a[i]<0){
            res.a[i+1]--; res.a[i]+=Base;
        }
    }
    while(!res.a[res.a[0]]) res.a[0]--;
    return res;
}
inline Int operator*(Int p,int q){
    int i; Int res=p;
    for(i=1;i<=res.a[0];i++) res.a[i]*=q;
    for(i=1;i<=res.a[0];i++){
        res.a[i+1]+=res.a[i]/Base; res.a[i]%=Base;
    }
    while(res.a[res.a[0]+1]){
        res.a[0]++; res.a[res.a[0]+1]+=res.a[res.a[0]]/Base; res.a[res.a[0]]%=Base;
    }
    return res;
}
inline Int operator*(Int p,Int q){
    int i,j; Int res; res.a[0]=p.a[0]+q.a[0];
    for(i=1;i<=p.a[0];i++) for(j=1;j<=q.a[0];j++){
        res.a[i+j-1]+=p.a[i]*q.a[j];
        res.a[i+j]+=res.a[i+j-1]/Base;
        res.a[i+j-1]%=Base;
    }
    while(!res.a[res.a[0]]) res.a[0]--;
    return res;
}
int main(){
    int i,j,n;
    Bin[0]=Int(1); for(i=1;i<=1500;i++) Bin[i]=Bin[i-1]*2;
    C[0][0]=Int(1);
    for(i=1;i<=50;i++){
        C[i][0]=Int(1);
        for(j=1;j<=i;j++) C[i][j]=C[i-1][j]+C[i-1][j-1];
    }
    Ans[1]=Int(1);
    for(i=2;i<=50;i++){
        Ans[i]=Bin[i*(i-1)/2];
        for(j=1;j<i;j++){
            Ans[i]=Ans[i]-Ans[j]*C[i-1][j-1]*Bin[(i-j)*((i-j)-1)/2];
        }
    }
    for(;;){
        R(n); if(n==0) break; Pl(Ans[n]);
    }
    return 0;
}

多项式求逆

公式推导见OI wiki
注意我们从(n+1)/2的子问题处理n的子问题时,是对一个取了(n+1)/2的模的子问题逆元和长度为2*n的原函数f(why?因为当前是模n意义下。f的长度肯定长于n。那么一个这样的多项式与模(n+1)/2的多项式乘积模n,就将两者补全为长度为2n的多项式进行计算,然后再取模)进行递推。
板子:

// 多项式求逆

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1e5+5;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch = getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}

const int P = 998244353;

inline int qpow(int x,int y){int res(1);while(y){if(y&1) res = 1ll*res*x%P;x = 1ll*x*x%P;y>>=1;}return res;}

int rev[MAXN<<2];

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 on){
    change(y,len);
    for(int h=2;h<=len;h<<=1){
        int gn = qpow(3,(P-1)/h);
        for(int j=0;j<len;j+=h){
            int g = 1;
            for(int k=j;k<j+h/2;++k){
                int u = y[k];
                int t = 1ll*g*y[k+h/2]%P;
                y[k] = (u+t)%P;
                y[k+h/2] = (u-t+P)%P;
                g = 1ll*g*gn%P;
            }
        }
    }
    if(on==-1) {
        reverse(y+1,y+len);
        register int inv = qpow(len,P-2);
        for(int i=0;i<len;++i) y[i] = 1ll*y[i]*inv%P;
    }
}

static int inv_t[MAXN<<2];

void solve(int f[],int g[],int n){
    // 递归:把两个n/2的合并成一个n的
    if(n==1) {f[0] = qpow(g[0],P-2);return;}
    solve(f,g,(n+1)>>1);
    int len(1);
    while(len<(n<<1)) len<<=1;
    copy(g,g+n,inv_t);
    fill(inv_t+n,inv_t+len,0);

    ntt(f,len,1);
    ntt(inv_t,len,1);
    for(int i=0;i<len;++i){
        f[i] = 1ll*f[i]*(2-1ll*f[i]*inv_t[i]%P+P)%P;
    }
    ntt(f,len,-1);
    fill(f+n,f+len,0);  // 取模
}


void polyinv(int f[], int h[], const int n) {
    fill(f, f + n + n, 0);
    f[0] = qpow(h[0], P - 2);
    for (int t = 2; t <= n; t <<= 1) {
        const int t2 = t << 1;
        copy(h, h + t, inv_t);
        fill(inv_t + t, inv_t + t2, 0);

        ntt(f, t2, 1);
        ntt(inv_t, t2, 1);
        for (int i = 0; i != t2; ++i)
            f[i] = 1ll*f[i]*(2-1ll*f[i]*inv_t[i]%P+P)%P;
        ntt(f, t2, -1);

        fill(f + t, f + t2, 0);
    }
}

int f[MAXN<<2],g[MAXN<<2];

int main(){
    //freopen("P4238_16.in","r",stdin);
    int n=read();
    for(int i=0;i<n;++i) g[i]=read();
    //cout<<g[i]<<" ";
    solve(f,g,n);
    //int len(1);
    //while(n>len) len<<=1;
    //polyinv(f,g,len);
    for(int i=0;i<n;++i)
        printf("%d%c",f[i],i==n-1?'\n':' ');
}

多项式开根

洛谷板子
跟多项式求逆异曲同工。

// 多项式开根

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1e5+5;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch = getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}

const int P = 998244353;

inline int qpow(int x,int y){int res(1);while(y){if(y&1) res = 1ll*res*x%P;x = 1ll*x*x%P;y>>=1;}return res;}

int rev[MAXN<<2];

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 on){
    change(y,len);
    for(int h=2;h<=len;h<<=1){
        int gn = qpow(3,(P-1)/h);
        for(int j=0;j<len;j+=h){
            int g = 1;
            for(int k=j;k<j+h/2;++k){
                int u = y[k];
                int t = 1ll*g*y[k+h/2]%P;
                y[k] = (u+t)%P;
                y[k+h/2] = (u-t+P)%P;
                g = 1ll*g*gn%P;
            }
        }
    }
    if(on==-1) {
        reverse(y+1,y+len);
        register int inv = qpow(len,P-2);
        for(int i=0;i<len;++i) y[i] = 1ll*y[i]*inv%P;
    }
}

static int sqrt_t[MAXN<<2],inv_t[MAXN<<2];
int invf[MAXN<<2];
int inv_two;

void getinv(int f[],int g[],int n){
    // 递归:把两个n/2的合并成一个n的
    if(n==1) {f[0] = qpow(g[0],P-2);return;}
    getinv(f,g,(n+1)>>1);
    int len(1);
    while(len<n<<1) len<<=1;
    copy(g,g+n,inv_t);
    fill(inv_t+n,inv_t+len,0);

    ntt(f,len,1);
    ntt(inv_t,len,1);
    for(int i=0;i<len;++i){
        f[i] = 1ll*f[i]*(2-1ll*f[i]*inv_t[i]%P+P)%P;
    }
    ntt(f,len,-1);
    fill(f+n,f+len,0);  // 取模
}

void solve(int f[],int g[],int n){
    // 递归:把两个n/2的合并成一个n的
    if(n==1) {f[0] = 1;return;} // 不需要二次剩余因为题目保证了a0=1
    solve(f,g,(n+1)>>1);
    int len(1);
    while(len<n<<1) len<<=1;
    copy(g,g+n,sqrt_t);
    fill(sqrt_t+n,sqrt_t+len,0);
    fill(invf,invf+len,0);
    getinv(invf,f,n);

    ntt(f,len,1);
    ntt(invf,len,1);
    ntt(sqrt_t,len,1);

    for(int i=0;i<len;++i){
        f[i] = (1ll*inv_two*f[i]%P+1ll*inv_two*invf[i]%P*sqrt_t[i]%P)%P;
    }
    ntt(f,len,-1);
    fill(f+n,f+len,0);  // 取模
}


int f[MAXN<<2],g[MAXN<<2];

int main(){
    int n=read();
    for(int i=0;i<n;++i) g[i]=read();
    inv_two = qpow(2,P-2);
    solve(f,g,n);
    for(int i=0;i<n;++i)
        printf("%d%c",f[i],i==n-1?'\n':' ');
}

多项式除法

多项式的带余数除法。
推公式就行。。用了一种神奇的思路。利用了取模。
模板

// 多项式除法

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+5;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch = getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}

const int P = 998244353;

inline int qpow(int x,int y){int res(1);while(y){if(y&1) res = 1ll*res*x%P;x = 1ll*x*x%P;y>>=1;}return res;}

int rev[MAXN<<2];

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 on){
    change(y,len);
    for(int h=2;h<=len;h<<=1){
        int gn = qpow(3,(P-1)/h);
        for(int j=0;j<len;j+=h){
            int g = 1;
            for(int k=j;k<j+h/2;++k){
                int u = y[k];
                int t = 1ll*g*y[k+h/2]%P;
                y[k] = (u+t)%P;
                y[k+h/2] = (u-t+P)%P;
                g = 1ll*g*gn%P;
            }
        }
    }
    if(on==-1) {
        reverse(y+1,y+len);
        register int inv = qpow(len,P-2);
        for(int i=0;i<len;++i) y[i] = 1ll*y[i]*inv%P;
    }
}

static int inv_t[MAXN<<2];

void solve(int f[],int g[],int n){
    // 递归:把两个n/2的合并成一个n的
    if(n==1) {f[0] = qpow(g[0],P-2);return;}
    solve(f,g,(n+1)>>1);
    int len(1);
    while(len<(n<<1)) len<<=1;
    copy(g,g+n,inv_t);
    fill(inv_t+n,inv_t+len,0);

    ntt(f,len,1);
    ntt(inv_t,len,1);
    for(int i=0;i<len;++i){
        f[i] = 1ll*f[i]*(2-1ll*f[i]*inv_t[i]%P+P)%P;
    }
    ntt(f,len,-1);
    fill(f+n,f+len,0);  // 取模
}

int f[MAXN<<2], g[MAXN<<2];
int f2[MAXN<<2], g2[MAXN<<2]; // 一个未取模的f
int invgr[MAXN<<2], q[MAXN<<2], r[MAXN<<2];

int main(){
    int n=read(),m=read();
    n++,m++;
    for(int i=0;i<n;++i) f[i]=read(),f2[i] = f[i];
    for(int i=0;i<m;++i) g[i]=read(),g2[i] = g[i];
    reverse(f,f+n);
    reverse(g,g+m);

    for(int i=n-m+1;i<m;++i) g[i] = 0;  // 取模
    fill(f+n-m+1,f+n,0);  // 取模
    solve(invgr,g,n-m+1);
    int len(1);
    while(len<((n-m+1)<<1)) len<<=1;
    ntt(invgr,len,1), ntt(f,len,1);
    for(int i=0;i<len;++i) q[i] = 1ll*invgr[i] * f[i] % P;
    ntt(q,len,-1);
    for(int i=0;i<n-m+1;++i){
        int j=n-m-i;
        if(i<j) swap(q[i],q[j]);
        printf("%d%c",q[i],i==n-m?'\n':' ');
    }
    for(int i=n-m+1;i<n;++i) q[i] = 0;
    // 不晓得为啥要清空,不是说q的阶小于n-m+1嘛

    while(len<(n<<1)) len<<=1;
    while(len<(m<<1)) len<<=1;
    ntt(f2,len,1), ntt(g2,len,1), ntt(q,len,1);
    // q要不要重新ntt呢?len改变了,所以我取了。。
    for(int i=0;i<len;++i)
        r[i] = (1ll*f2[i] - 1ll*g2[i]*q[i]%P + P)%P;
    ntt(r,len,-1);
    for(int i=0;i<m-1;++i)
        printf("%d%c",r[i],i==m-2?'\n':' ');
}

分治FFT

板子
法一:生成函数+多项式求逆
We have f [ i ] = ∑ j = 1 i f [ i − j ] × g [ j ] , i > 0 f[i]=\sum_{j=1}^i f[i-j]\times g[j],i>0 f[i]=j=1if[ij]×g[j],i>0.
define f ( x ) = ∑ i = 0 ∞ f [ i ] x i f(x)=\sum_{i=0}^\infty f[i]x^i f(x)=i=0f[i]xi
and g ( x ) = ∑ i = 0 ∞ g [ i ] x i g(x)=\sum_{i=0}^\infty g[i]x^i g(x)=i=0g[i]xi.
(let g [ 0 ] = 0 g[0]=0 g[0]=0)
convolute:
f ( x ) ∗ g ( x ) = ∑ i = 0 ∞ ∑ j = 0 ∞ f [ i ] g [ j ] x i + j f(x)*g(x)=\sum_{i=0}^\infty \sum_{j=0}^\infty f[i] g[j]x^{i+j} f(x)g(x)=i=0j=0f[i]g[j]xi+j
let k = i + j k=i+j k=i+j
f ( x ) ∗ g ( x ) = ∑ k = 0 ∞ ( ∑ j = 0 k f [ k − j ] g [ j ] ) x k f(x)*g(x)=\sum_{k=0}^\infty( \sum_{j=0}^k f[k-j]g[j])x^k f(x)g(x)=k=0(j=0kf[kj]g[j])xk
when k > 0 k>0 k>0, ∑ j = 0 k f [ k − j ] g [ j ] = f [ k ] \sum_{j=0}^k f[k-j]g[j]=f[k] j=0kf[kj]g[j]=f[k]
when k = 0 k=0 k=0, f [ 0 ] g [ 0 ] = 0 f[0]g[0]=0 f[0]g[0]=0, ( g [ 0 ] = 0 ) (g[0]=0) (g[0]=0)
therefore,
f ( x ) ∗ g ( x ) = ∑ k = 1 ∞ f [ k ] x k = f ( x ) − f [ 0 ] f(x)*g(x)=\sum_{k=1}^\infty f[k]x^k=f(x)-f[0] f(x)g(x)=k=1f[k]xk=f(x)f[0]
we have f ( x ) = f [ 0 ] 1 − g ( x ) f(x)=\frac{f[0]}{1-g(x)} f(x)=1g(x)f[0].(多项式求逆)

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1e5+5;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch = getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}

const int P = 998244353;

inline int qpow(int x,int y){int res(1);while(y){if(y&1) res = 1ll*res*x%P;x = 1ll*x*x%P;y>>=1;}return res;}

int rev[MAXN<<2];

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 on){
    change(y,len);
    for(int h=2;h<=len;h<<=1){
        int gn = qpow(3,(P-1)/h);
        for(int j=0;j<len;j+=h){
            int g = 1;
            for(int k=j;k<j+h/2;++k){
                int u = y[k];
                int t = 1ll*g*y[k+h/2]%P;
                y[k] = (u+t)%P;
                y[k+h/2] = (u-t+P)%P;
                g = 1ll*g*gn%P;
            }
        }
    }
    if(on==-1) {
        reverse(y+1,y+len);
        register int inv = qpow(len,P-2);
        for(int i=0;i<len;++i) y[i] = 1ll*y[i]*inv%P;
    }
}

static int inv_t[MAXN<<2];

void solve(int f[],int g[],int n){
    // 递归:把两个n/2的合并成一个n的
    if(n==1) {f[0] = qpow(g[0],P-2);return;}
    solve(f,g,(n+1)>>1);
    int len(1);
    while(len<(n<<1)) len<<=1;
    copy(g,g+n,inv_t);
    fill(inv_t+n,inv_t+len,0);

    ntt(f,len,1);
    ntt(inv_t,len,1);
    for(int i=0;i<len;++i){
        f[i] = 1ll*f[i]*(2-1ll*f[i]*inv_t[i]%P+P)%P;
    }
    ntt(f,len,-1);
    fill(f+n,f+len,0);  // 取模
}

int invg[MAXN<<2],g[MAXN<<2];

int main(){
    int n=read();
    for(int i=1;i<n;++i) g[i]=read(),g[i]=(P-g[i])%P;
    g[0]=1;
    solve(invg,g,n);
    for(int i=0;i<n;++i){
        printf("%d%c",invg[i],(i==n-1)?'\n':' ');
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值