Skip to content

多项式 ​

cdq分治FFT ​

给定序列 g1…n−1g_{1\dots n - 1},求序列 f0…n−1f_{0\dots n - 1}。

其中 fi=∑j=1ifi−jgjf_i=\sum_{j=1}^if_{i-j}g_j,边界为 f0=1f_0=1。

答案对 998244353998244353 取模。

考虑递归计算计算flf_l到fmidf_{mid},接着计算后半部分贡献

wx=∑i=lmidfi×gx−iwx=∑i=lx−1fi×gx−iwx=∑i=0x−l−1fi+l×gx−i−l设ai=fi+l,bi=gi+1wx=∑i=0x−l−1ai×bx−l−1−iw_x =\sum_{i=l}^{mid}f_i \times g_{x-i}\\ w_x=\sum_{i=l}^{x-1}f_i \times g_{x-i}\\ w_x=\sum_{i=0}^{x-l-1}f_{i+l} \times g_{x-i-l} 设a_i=f_{i+l},b_i=g_{i+1} w_x=\sum_{i=0}^{x-l-1}a_i \times b_{x-l-1-i}

cpp
namespace NTT{
    static constexpr int P=998244353;
    int rev[N],A[N],B[N],bit=0,tot,g=3,gi;
    int qpow(int a,int b){
        int ans=1;
        for(;b;b>>=1){
            if(b&1)ans=ans*a%P;
            a=a*a%P;
        }
        return ans;
    }
    int inv(int n){
        return qpow(n,P-2);
    }
    void ntt(int *a,int inv){
        for(int i=0;i<tot;i++){
            if(i<rev[i])swap(a[i],a[rev[i]]);
        }
        for(int mid=1;mid<tot;mid<<=1){
            int g1=qpow(inv==1?g:gi,(P-1)/(mid*2));
            for(int i=0;i<tot;i+=mid*2){
                int gk=1;
                for(int j=0;j<mid;j++,gk=gk*g1%P){
                    auto x=a[i+j],y=gk*a[i+j+mid]%P;
                    a[i+j]=(x+y)%P,a[i+j+mid]=(x-y+P)%P;
                }
            }
        }
        if(inv==-1){
            int tmp=NTT::inv(tot);
            for(int i=0;i<tot;i++)a[i]=a[i]*tmp%P;
        }
    }
    void init(int n){
        bit=0;
        while((1<<bit)<n)bit++;
        tot=1<<bit;
        for(int i=0;i<tot;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
        gi=inv(g);
    }
    void mul(int *a,int *b){
        ntt(a,1),ntt(b,1);
        for(int i=0;i<tot;i++)a[i]=a[i]*b[i]%P;
        ntt(a,-1);
    }
    void cdq_ntt(int *a,int *b,int l,int r){
        if(l==r)return;
        int mid=l+r>>1,len=r-l+1;
        cdq_ntt(a,b,l,mid);
        init(len);
        for(int i=l;i<=mid;i++)A[i-l]=a[i];
        for(int i=1;i<=r-l;i++)B[i-1]=b[i];
        mul(A,B);
        for(int i=mid+1;i<=r;i++)(a[i]+=A[i-l-1])%=P;
        for(int i=0;i<tot;i++)A[i]=B[i]=0;
        cdq_ntt(a,b,mid+1,r);
    }
}
int f[N],g[N];
void solve(){
    int n;
    cin>>n;
    f[0]=1;
    for(int i=1;i<n;i++)cin>>g[i];
    NTT::cdq_ntt(f,g,0,n-1);
    for(int i=0;i<n;i++)cout<<f[i]<<" \n"[i==n-1];
}

板子集合 ​

cpp

namespace Poly{
    using poly=vector<int>;
    vector<int> rev;
    const int P=998244353;
    int qpow(int a,int b){
        int ans=1;
        for(;b;b>>=1){
            if(b&1)ans=ans*a%P;
            a=a*a%P;
        }
        return ans;
    }
    int bit=0,tot,g=3,gi=qpow(g,P-2);
    void ntt(poly &a,int inv){
        for(int i=0;i<tot;i++){
            if(i<rev[i])swap(a[i],a[rev[i]]);
        }
        for(int mid=1;mid<tot;mid<<=1){
            int g1=qpow(inv==1?g:gi,(P-1)/(mid*2));
            for(int i=0;i<tot;i+=mid*2){
                int gk=1;
                for(int j=0;j<mid;j++,gk=gk*g1%P){
                    auto x=a[i+j],y=gk*a[i+j+mid]%P;
                    a[i+j]=(x+y)%P,a[i+j+mid]=(x-y+P)%P;
                }
            }
        }
        if(inv==-1){
            int invn=qpow(tot,P-2);
            for(int i=0;i<tot;i++)a[i]=a[i]*invn%P;
        }
    }
    void init(int n){
        bit=0;
        while((1<<bit)<n)bit++;
        tot=1<<bit;
        rev.resize(tot);
        for(int i=0;i<tot;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    }
    // 多项式乘法
    poly mul(poly a,poly b){
        int n=a.size(),m=b.size();
        init(n+m+1);
        a.resize(tot),b.resize(tot);
        ntt(a,1),ntt(b,1);
        for(int i=0;i<tot;i++)a[i]=a[i]*b[i]%P;
        ntt(a,-1);
        for(int i=n+m;i<tot;i++)a[i]=0;
        return a;
    }
    // 多项式求逆
    poly inv(const poly &f,int n){
        if(n==1)return {qpow(f[0],P-2)};
        auto g=inv(f,n+1>>1);
        static vector<int> c;
        init(2*n+1);
        c.resize(tot);
        g.resize(tot);
        for(int i=0;i<n;i++)c[i]=f[i];
        for(int i=n;i<tot;i++)c[i]=0;
        ntt(c,1),ntt(g,1);
        for(int i=0;i<tot;i++)g[i]=g[i]*(2-c[i]*g[i]%P+P)%P;
        ntt(g,-1);
        for(int i=n;i<tot;i++)g[i]=0;
        return g;
    }
    // 多项式求导
    poly der(const poly &f){
        int n=f.size();
        poly g(n-1);
        for(int i=1;i<n;i++)g[i-1]=f[i]*i%P;
        return g;
    }
    // 多项式求积分
    poly inte(const poly &f){
        int n=f.size();
        poly g(n+1);
        g[0]=0;
        for(int i=1;i<=n;i++)g[i]=f[i-1]*qpow(i,P-2)%P;
        return g;
    }
    // 多项式求值
    int eval(const poly &f,int x){
        int n=f.size(),ans=0;
        for(int i=n-1;i>=0;i--)ans=(ans*x+f[i])%P;
        return ans;
    }
    // 多项式翻转Ar(x)=x^n*A(1/x)
    poly reverse(const poly &f,int n){
        poly g(n);
        for(int i=0;i<n;i++)g[i]=f[n-i-1];
        return g;
    }
    // 多项式加法
    poly add(const poly &f,const poly &g){
        poly c(max(f.size(),g.size()));
        for(int i=0;i<max(f.size(),g.size());i++){
            if(i<f.size())c[i]=f[i];
            if(i<g.size())c[i]=(c[i]+g[i])%P;
        }
        return c;
    }
    // 多项式减法
    poly sub(const poly &f,const poly &g){
        poly c(max(f.size(),g.size()));
        for(int i=0;i<max(f.size(),g.size());i++){
            if(i<f.size())c[i]=f[i];
            if(i<g.size())c[i]=(c[i]-g[i]+P)%P;
        }
        return c;
    }
    // 多项式除法
    pair<poly,poly> div(const poly &f,const poly &g){
        int n=f.size(),m=g.size(),len=n-m+1;
        if(n<m)return {{},f};
        poly fr=reverse(f,n),gr=reverse(g,m);
        gr.resize(len);
        for(int i=len;i<m;i++)gr[i]=0;
        gr=inv(gr,len);
        poly q=mul(fr,gr);
        q=reverse(q,len);
        poly r=sub(f,mul(q,g));
        return {q,r};
    }
    // cipolla
    int cipolla(int x){
        if(qpow(x,P-1>>1)==P-1)return -1;
        while(1){
            int a=rng()%P;
            int w=(a*a%P-x+P)%P;
            auto mul=[&](PII a,PII b){
                return PII{(a.F*b.F%P+w*a.S%P*b.S%P)%P,(a.F*b.S%P+a.S*b.F%P)%P};
            };
            auto pow=[&](PII a,int b){
                PII res{1,0};
                for(;b;b>>=1){
                    if(b&1)res=mul(res,a);
                    a=mul(a,a);
                }
                return res.F;
            };
            if(qpow(w,P-1>>1)==P-1){
                int res=pow(PII{a,1},P+1>>1);
                return min(res,P-res);
            }
        }
    }
    // 多项式开根(a0!=1)
    poly sqrt(const poly &f,int n){
        if(n==1){
            if(f[0]==1)return {1};
            return {cipolla(f[0])};
        }
        auto g=sqrt(f,n+1>>1);
        static vector<int> c;
        static const int inv2=qpow(2,P-2);
        init(2*n+1);
        c.resize(tot);
        g.resize(tot);
        for(int i=0;i<n;i++)c[i]=f[i];
        for(int i=n;i<tot;i++)c[i]=0;
        auto ig=inv(g,n);
        ntt(g,1),ntt(ig,1),ntt(c,1);
        for(int i=0;i<tot;i++)g[i]=(g[i]*g[i]%P+c[i])%P*ig[i]%P*inv2%P;
        ntt(g,-1);
        for(int i=n;i<tot;i++)g[i]=0;
        return g;
    }   
    // 多项式对数ln
    poly ln(const poly &f){
        return inte(mul(der(f),inv(f,f.size())));
    }
    // 多项式指数exp
    poly exp(const poly &f,int n){
        if(n==1)return {1};
        poly g=exp(f,n+1>>1);
        g.resize(n);
        poly lng=ln(g);
        init(2*n+1);
        g.resize(tot);
        lng.resize(tot);
        for(int i=0;i<n;i++)lng[i]=(f[i]-lng[i]+P)%P;
        for(int i=n;i<tot;i++)lng[i]=g[i]=0;
        lng[0]++;
        ntt(lng,1);
        ntt(g,1);
        for(int i=0;i<tot;i++)g[i]=g[i]*lng[i]%P;
        ntt(g,-1);
        for(int i=n;i<tot;i++)g[i]=0;
        return g;
    }
    // 多项式快速幂
    poly qpow(const poly &f,int b){
        int n=f.size();
        poly res{1},a=f;
        res[0]=1;
        init(n<<1);
        res.resize(tot);
        a.resize(tot);
        for(;b;b>>=1){
            ntt(a,1);
            if(b&1){
                ntt(res,1);
                for(int i=0;i<tot;i++)res[i]=res[i]*a[i]%P;
                ntt(res,-1);
                for(int i=n;i<tot;i++)res[i]=0;
            }
            for(int i=0;i<tot;i++)a[i]=a[i]*a[i]%P;
            ntt(a,-1);
            for(int i=n;i<tot;i++)a[i]=0;
        }
        return res;
    }
    // 多项式多点求值
    vector<int> evals(const poly &f,vector<int> &x){
        vector<poly> T;
        int n=f.size(),m=x.size();
        T.assign(m<<2,{});
        function<void(int,int,int)> build=[&](int p,int l,int r){
            if(l==r){
                T[p]={(P-x[l])%P,1};
                return;
            }
            int mid=l+r>>1;
            build(p<<1,l,mid),build(p<<1|1,mid+1,r);
            T[p]=mul(T[p<<1],T[p<<1|1]);
            while(T[p].size()>1&&T[p].back()==0)T[p].pop_back();
        };
        vector<int> res(m);
        function<void(int,int,int,poly)> solve=[&](int p,int l,int r,poly f){
            if(r-l+1<=64){
                for(int i=l;i<=r;i++){
                    res[i]=eval(f,x[i]);
                }
                return;
            }
            auto [q1,r1]=div(f,T[p<<1]);
            auto [q2,r2]=div(f,T[p<<1|1]);
            while(r1.size()>1&&r1.back()==0)r1.pop_back();
            while(r2.size()>1&&r2.back()==0)r2.pop_back();
            int mid=l+r>>1;
            solve(p<<1,l,mid,r1);
            solve(p<<1|1,mid+1,r,r2);
        };
        build(1,0,m-1);
        if(f.size()>=T[1].size()){
            auto [q,r]=div(f,T[1]);
            solve(1,0,m-1,r);
        }
        else solve(1,0,m-1,f);
        return res;
    }
}

modint板子 ​

cpp
struct mint {
    constexpr static int mod = 998244353;
	int x;
	inline mint(int o = 0) { x = o%mod; }
	inline mint & operator = (int o) { return x = o%mod, *this; }
	inline mint & operator += (mint o) { return (x += o.x) >= mod && (x -= mod), *this; }
	inline mint & operator -= (mint o) { return (x -= o.x) < 0 && (x += mod), *this; }
	inline mint & operator *= (mint o) { return x = (long long) x * o.x % mod, *this; }
    inline mint& qpow(int b){
        mint w = *this;
        mint ret(1);
        for(; b; b >>= 1, w *= w) if(b & 1) ret *= w;
        return x = ret.x, *this;
    }
	inline mint & operator /= (mint o) { return *this *= (o .qpow( (mod - 2))); }
	friend inline mint operator + (mint a, mint b) { return a += b; }
	friend inline mint operator - (mint a, mint b) { return a -= b; }
	friend inline mint operator * (mint a, mint b) { return a *= b; }
	friend inline mint operator / (mint a, mint b) { return a /= b; }
    friend ostream& operator<<(ostream& os, const mint& m) {
        return os << m.x;
    }
};

拉格朗日插值 ​

n+1n+1个数确定nn次多项式, f(k)=∑i=0nyi∏k−xjxi−xjf(k)=\sum_{i=0}^n y_i \prod \frac{k-x_j}{x_i-x_j}

O(n^2)版本 ​

cpp
int lagrange(int k,vector<int> &X,vector<int> &Y){
    int ans=0;
    int n=X.size();
    for(int i=0;i<n;i++){
        int fz=1,fm=1;
        for(int j=0;j<n;j++){
            if(i==j)continue;
            fz=fz*(k-X[j])%mod;
            fm=fm*(X[i]-X[j])%mod;
        }
        ans=(ans+Y[i]*fz%mod*qpow(fm,mod-2)%mod)%mod;
    }
    return (ans+mod)%mod;
}

O(n)版本 ​

  • 如果xx取值连续,那么把xix_i换成ii,f(k)=∑i=0nyi∏k−ii−jf(k)=\sum_{i=0}^ny_i \prod \frac{k-i}{i-j}
  • 记prei=∏j=0ik−j,sufi=∏j=ink−jpre_i=\prod_{j=0}^i k-j,suf_i=\prod_{j=i}^n k-j
  • f(k)=∑i=0nyiprei−1sufi+1faci−1facn−if(k)=\sum_{i=0}^ny_i\frac{pre_{i-1}suf_{i+1}}{fac_{i-1}fac_{n-i}},n−in-i为奇数时分母取负
cpp
int lagrange(int k,vector<int> &X,vector<int> &Y,int n){
    int ans=0;
    vector<int> pre(n+10),suf(n+10),ifac(n+10);
    pre[0]=(k-X[0])%mod,suf[n+1]=1;
    for(int i=1;i<=n;i++)pre[i]=pre[i-1]*(k-X[i])%mod;
    for(int i=n;i>=0;i--)suf[i]=suf[i+1]*(k-X[i])%mod;
    ifac[0]=ifac[1]=1;
    for(int i=2;i<=n;i++)ifac[i]=-1ll*mod/i*ifac[mod%i]%mod;
    for(int i=2;i<=n;i++)ifac[i]=ifac[i]*ifac[i-1]%mod;
    for(int i=0;i<=n;i++){
        int tmp=Y[i]*(i==0?1:pre[i-1])%mod*suf[i+1]%mod;
        tmp=tmp*ifac[i]%mod*ifac[n-i]%mod;
        if(n-i&1)tmp=mod-tmp;
        ans=(ans+tmp)%mod;
    }
    return (ans+mod)%mod;
}

求∑i=1nik\sum_{i=1}^n i^k ​

复杂度O(k)O(k)

cpp
void solve(){
    int n,k;
    cin>>n>>k;
    vector<int> X(k+2),Y(k+2);
    iota(X.begin(),X.end(),1);
    Y[0]=1;
    for(int i=1;i<=k+1;i++)Y[i]=(Y[i-1]+qpow(i+1,k))%mod;
    cout<<lagrange(n,X,Y,k+1);
}

重心拉格朗日插值法 ​

  • g=∏i=0nk−xi,ti=yi∏xi−xjg=\prod _{i=0}^nk-x_i,t_i=\frac{y_i}{\prod x_i-x_j}
  • f(k)=g∑i=0ntik−xif(k)=g \sum_{i=0}^n\frac{t_i}{k-x_i} 每新加入一个点只需要计算tit_i即可,预处理仍然是O(n2)O(n^2),但是插入是O(n)O(n)的

多项式乘法逆 ​

给定一个多项式 F(x)F(x) ,请求出一个多项式 G(x)G(x), 满足 F(x)∗G(x)≡1(modxn)F(x) * G(x) \equiv 1 \pmod{x^n}。系数对 998244353998244353 取模。

牛顿迭代法 已知一个复合多项式 G(F(x))≡0(mod  xn)G(F(x)) \equiv 0(\mod x^n),假设 F0(x)≡F(x)(mod  xn2),F1(x)≡F(x)(mod  xn)F_0(x) \equiv F(x)(\mod x^{\frac{n}{2}}),F_1(x) \equiv F(x)(\mod x^n)

泰勒公式在F0(x)F_0(x)展开G(F(x))G(F(x)),G(F(x))=∑i=0∞G(i)F0(x)i!(F(x)−F0(x))iG(F(x))=\sum_{i=0}^{\infty}\frac{G^{(i)}F_0(x)}{i!}(F(x)-F_0(x))^i

G(F1(x))≡G(F0(x))+G′(F0(x))(F1(x)−F0(x))(mod  xn)G(F_1(x)) \equiv G(F_0(x))+G'(F_0(x))(F_1(x)-F_0(x))(\mod x^n)

F1(x)=F0(x)−G(F0(x))G′(F0(x))F_1(x)=F_0(x)-\frac{G(F_0(x))}{G'(F_0(x))}

将H(t)=F(x)−1tH(t)=F(x)-\frac{1}{t}套进去

G1(x)=G0(x)−F(x)−1G0(x)1G0(x)2G_1(x)=G_0(x)-\frac{F(x)-\frac{1}{G_0(x)}}{\frac{1}{G_0(x)^2}}

G1(x)=G0(x)(2−G0(x)F(x))G_1(x)=G_0(x)(2-G_0(x)F(x))

cpp
poly inv(poly &f,int n){
    if(n==1)return {qpow(f[0],P-2)};
    auto g=inv(f,n+1>>1);
    static vector<int> c;
    init(2*n+1);
    c.resize(tot);
    g.resize(tot);
    for(int i=0;i<n;i++)c[i]=f[i];
    for(int i=n;i<tot;i++)c[i]=0;
    ntt(c,1),ntt(g,1);
    for(int i=0;i<tot;i++)g[i]=g[i]*(2-c[i]*g[i]%P+P)%P;
    ntt(g,-1);
    for(int i=n;i<tot;i++)g[i]=0;
    return g;
}
void solve(){
    int n;
    cin>>n;
    poly f(n+1);
    for(int i=0;i<n;i++)cin>>f[i];
    auto g=inv(f,n);
    for(int i=0;i<n;i++)cout<<g[i]<<" ";
}

多项式求导 ​

cpp
poly der(poly &f){
    int n=f.size();
    poly g(n-1);
    for(int i=1;i<n;i++)g[i-1]=f[i]*i%P;
    return g;
}

多项式求积分 ​

cpp
poly inte(poly &f){
    int n=f.size();
    poly g(n+1);
    g[0]=0;
    for(int i=1;i<=n;i++)g[i]=f[i-1]*qpow(i,P-2)%P;
    return g;
}

多项式除法 ​

给定一个 nn 次多项式 F(x)F(x) 和一个 mm 次多项式 G(x)G(x) ,请求出多项式 Q(x)Q(x), R(x)R(x),满足以下条件:

  • Q(x)Q(x) 次数为 n−mn-m,R(x)R(x) 次数小于 mm
  • F(x)=Q(x)∗G(x)+R(x)F(x) = Q(x) * G(x) + R(x)

考虑一种翻转操作,AR(x)=xnA(1x)A_R(x)=x^nA(\frac{1}{x}),即An−i=AiA_{n-i}=A_{i}

F(1x)=Q(1x)G(1x)+R(1x)F(\frac{1}{x})=Q(\frac{1}{x})G(\frac{1}{x})+R(\frac{1}{x})

xnF(1x)=xn−mQ(1x)∗xmG(1x)+xn−m+1∗xm−1R(1x)x^nF(\frac{1}{x})=x^{n-m}Q(\frac{1}{x}) * x^mG(\frac{1}{x})+x^{n-m+1}*x^{m-1}R(\frac{1}{x})

FR(x)=QR(x)GR(x)+xn−m+1RR(x)F_R(x)=Q_R(x)G_R(x)+x^{n-m+1}R_R(x)

FR(x)≡QR(x)GR(x)(mod  xn−m+1)F_R(x) \equiv Q_R(x)G_R(x)(\mod x^{n-m+1})

QR(x)≡FR(x)GR−1(x)(mod  xn−m+1)Q_R(x) \equiv F_R(x)G_R^{-1}(x)(\mod x^{n-m+1})

最后R(x)=F(x)−G(x)Q(x)R(x)=F(x)-G(x)Q(x)

cpp
pair<poly,poly> div(const poly &f,const poly &g){
    int n=f.size(),m=g.size(),len=n-m+1;
    poly fr=reverse(f,n),gr=reverse(g,m);
    gr.resize(len);
    for(int i=len;i<m;i++)gr[i]=0;
    gr=inv(gr,len);
    poly q=mul(fr,gr,n,len);
    q=reverse(q,len);
    poly r=sub(f,mul(q,g,len,m));
    return {q,r};
}
void solve(){
    int n,m;
    cin>>n>>m;
    poly f(n+1),g(m+1);
    for(int i=0;i<=n;i++)cin>>f[i];
    for(int i=0;i<=m;i++)cin>>g[i];
    auto [q,r]=div(f,g);
    for(int i=0;i<=n-m;i++)cout<<q[i]<<" \n"[i==n-m];
    for(int i=0;i<m;i++)cout<<r[i]<<" \n"[i==m-1];
}

多项式开根(a0=1) ​

给定一个 n−1n-1 次多项式 A(x)A(x),求一个在  mod xn{} \bmod x^n 意义下的多项式 B(x)B(x),使得 B2(x)≡A(x)(modxn)B^2(x) \equiv A(x) \pmod{x^n}。若有多解,请取零次项系数较小的作为答案。

多项式的系数在  mod 998244353{}\bmod 998244353 的意义下进行运算。

牛顿迭代法,构造式子H(t)=t2−F(x)H(t)=t^2-F(x)

带入式子得G1(x)=G0(x)−G0(x)2−F(x)2G0(x)=G0(x)2+F(x)2G0(x)=12(G0(x)+F(x)G0(x))G_1(x)=G_0(x)-\frac{G_0(x)^2-F(x)}{2G_0(x)}=\frac{G_0(x)^2+F(x)}{2G_0(x)}=\frac{1}{2}(G_0(x)+\frac{F(x)}{G_0(x)})

cpp
poly sqrt(const poly &f,int n){
    if(n==1)return {1};
    auto g=sqrt(f,n+1>>1);
    static vector<int> c;
    static const int inv2=qpow(2,P-2);
    init(2*n+1);
    c.resize(tot);
    g.resize(tot);
    for(int i=0;i<n;i++)c[i]=f[i];
    for(int i=n;i<tot;i++)c[i]=0;
    auto ig=inv(g,n);
    ntt(g,1),ntt(ig,1),ntt(c,1);
    for(int i=0;i<tot;i++)g[i]=(g[i]*g[i]%P+c[i])%P*ig[i]%P*inv2%P;
    ntt(g,-1);
    for(int i=n;i<tot;i++)g[i]=0;
    return g;
}  
void solve(){
    int n;
    cin>>n;
    poly f(n+1);
    for(int i=0;i<n;i++)cin>>f[i];
    auto g=sqrt(f,n+1);
    for(int i=0;i<n;i++)cout<<g[i]<<" ";
}

多项式开根a0!=1 ​

需要检验a0是否有二次剩余,即x2≡a0(mod  998244353)x^2 \equiv a_0(\mod 998244353)是否成立

cpp
int cipolla(int x){
    if(qpow(x,P-1>>1)==P-1)return -1;
    while(1){
        int a=rng()%P;
        int w=(a*a%P-x+P)%P;
        auto mul=[&](PII a,PII b){
            return PII{(a.F*b.F%P+w*a.S%P*b.S%P)%P,(a.F*b.S%P+a.S*b.F%P)%P};
        };
        auto pow=[&](PII a,int b){
            PII res{1,0};
            for(;b;b>>=1){
                if(b&1)res=mul(res,a);
                a=mul(a,a);
            }
            return res.F;
        };
        if(qpow(w,P-1>>1)==P-1){
            int res=pow(PII{a,1},P+1>>1);
            return min(res,P-res);
        }
    }
}
// 多项式开根(a0!=1)
poly sqrt(const poly &f,int n){
    if(n==1){
        if(f[0]==1)return {1};
        return {cipolla(f[0])};
    }
    auto g=sqrt(f,n+1>>1);
    static vector<int> c;
    static const int inv2=qpow(2,P-2);
    init(2*n+1);
    c.resize(tot);
    g.resize(tot);
    for(int i=0;i<n;i++)c[i]=f[i];
    for(int i=n;i<tot;i++)c[i]=0;
    auto ig=inv(g,n);
    ntt(g,1),ntt(ig,1),ntt(c,1);
    for(int i=0;i<tot;i++)g[i]=(g[i]*g[i]%P+c[i])%P*ig[i]%P*inv2%P;
    ntt(g,-1);
    for(int i=n;i<tot;i++)g[i]=0;
    return g;
}

多项式对数函数(多项式 ln)(a_0=1) ​

给出 n−1n-1 次多项式 A(x)A(x),求一个  mod  xn\bmod{\:x^n} 下的多项式 B(x)B(x),满足 B(x)≡ln⁡A(x)B(x) \equiv \ln A(x).

在 mod 998244353\text{mod } 998244353 下进行,且 ai∈[0,998244353)∩Za_i \in [0, 998244353) \cap \mathbb{Z}

保证 a0=1a_0 = 1.

令 f(x)=ln⁡xf(x)=\ln x,G(x)≡f(F(x))(mod  xn)G(x) \equiv f(F(x))(\mod x^n)

两边求导 G′(x)=f′(F(x))F′(x)=F′(x)F(x)G'(x)=f'(F(x))F'(x)=\frac{F'(x)}{F(x)}

求出来后再积回去

cpp
poly ln(const poly &f,int n){
    return inte(mul(der(f),inv(f,n)));
}

多项式指数函数(多项式 exp)(a_0=0) ​

给出 n−1n-1 次多项式 A(x)A(x),求一个  mod  xn\bmod{\:x^n} 下的多项式 B(x)B(x),满足 B(x)≡eA(x)B(x) \equiv \text e^{A(x)}。系数对 998244353998244353 取模。

保证 a0=0a_0 = 0.

牛顿迭代法,H(t)=ln⁡t−F(x)H(t)=\ln t-F(x),G1(x)≡G0(x)(1−ln⁡G0(x)+F(x))G_1(x)\equiv G_0(x)(1-\ln G_0(x)+F(x))

cpp
poly exp(const poly &f,int n){
    if(n==1)return {1};
    poly g=exp(f,n+1>>1);
    g.resize(n);
    poly lng=ln(g);
    init(2*n+1);
    g.resize(tot);
    lng.resize(tot);
    for(int i=0;i<n;i++)lng[i]=(f[i]-lng[i]+P)%P;
    for(int i=n;i<tot;i++)lng[i]=g[i]=0;
    lng[0]++;
    ntt(lng,1);
    ntt(g,1);
    for(int i=0;i<tot;i++)g[i]=g[i]*lng[i]%P;
    ntt(g,-1);
    for(int i=n;i<tot;i++)g[i]=0;
    return g;
}

多项式多点求值 ​

cpp
// 多项式多点求值
vector<int> evals(const poly &f,vector<int> &x){
    vector<poly> T;
    int n=f.size(),m=x.size();
    T.assign(m<<2,{});
    function<void(int,int,int)> build=[&](int p,int l,int r){
        if(l==r){
            T[p]={(P-x[l])%P,1};
            return;
        }
        int mid=l+r>>1;
        build(p<<1,l,mid),build(p<<1|1,mid+1,r);
        T[p]=mul(T[p<<1],T[p<<1|1]);
        while(T[p].size()>1&&T[p].back()==0)T[p].pop_back();
    };
    vector<int> res(m);
    function<void(int,int,int,poly)> solve=[&](int p,int l,int r,poly f){
        if(r-l+1<=64){
            for(int i=l;i<=r;i++){
                res[i]=eval(f,x[i]);
            }
            return;
        }
        auto [q1,r1]=div(f,T[p<<1]);
        auto [q2,r2]=div(f,T[p<<1|1]);
        while(r1.size()>1&&r1.back()==0)r1.pop_back();
        while(r2.size()>1&&r2.back()==0)r2.pop_back();
        int mid=l+r>>1;
        solve(p<<1,l,mid,r1);
        solve(p<<1|1,mid+1,r,r2);
    };
    build(1,0,m-1);
    if(f.size()>=T[1].size()){
        auto [q,r]=div(f,T[1]);
        solve(1,0,m-1,r);
    }
    else solve(1,0,m-1,f);
    return res;
}
void solve(){
    int n,m;
    cin>>n>>m;
    n++;
    vector<int> f(n),a(m);
    for(int i=0;i<n;i++)cin>>f[i];
    for(int i=0;i<m;i++)cin>>a[i];
    auto ans=evals(f,a);
    for(int i=0;i<m;i++)cout<<ans[i]<<"\n";
}

多项式快速幂(a_0=1) ​

  • 首先对k直接取模,然后直接算,复杂度O(nlog⁡nlog⁡(kmod  998244353))O(n \log n \log (k \mod 998244353))
cpp
poly qpow(const poly &f,int b){
    int n=f.size();
    poly res{1},a=f;
    res[0]=1;
    init(n<<1);
    res.resize(tot);
    a.resize(tot);
    ntt(res,1),ntt(a,1);
    for(;b;b>>=1){
        if(b&1){
            for(int i=0;i<tot;i++)res[i]=res[i]*a[i]%P;
            ntt(res,-1);
            for(int i=n;i<tot;i++)res[i]=0;
            ntt(res,1);
        }
        for(int i=0;i<tot;i++)a[i]=a[i]*a[i]%P;
        ntt(a,-1);
        for(int i=n;i<tot;i++)a[i]=0;
        ntt(a,1);
    }
    ntt(res,-1);
    return res;
}
  • 都取对数ln⁡B(x)=kln⁡A(x)\ln B(x)=k \ln A(x),先算出右边,再exp回去
  • 复杂度O(nlog⁡n)O(n \log n)
cpp
poly qpow(const poly &f,int b){
    poly lnf=ln(f);
    for(int i=0;i<lnf.size();i++)lnf[i]=lnf[i]*b%P;
    return exp(lnf,f.size());
}

多项式复合逆 ​

令 n−1n-1 次多项式 F(x)=∑i=0n−1aixiF(x)=\sum\limits _{i=0}^{n-1} a_ix^i。

给定 nn 和 F(x)F(x) 的各项系数,要求一个 n−1n-1 次多项式 G(x)G(x) 满足:

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

求 G(x)G(x) 的各项系数对 998244353998244353 取模的结果。

保证 a0=0a_0=0,a1≠0a_1\neq 0。

生成函数 ​

概念 ​

g(x)=a0+a1x+a2x2+...+anxn(−1≤x≤1)g(x)=a_0+a_1x+a_2x^2+...+a_nx^n(-1 \leq x \leq 1)

常用公式 ​

A(x)=∑n=0∞anxnA(x)=\sum_{n=0}^{\infty}a_nx^n

  1. 求∑n=0K\sum_{n=0}^K的方法为,求A(x)1−x\frac{A(x)}{1-x}的xKx^K的系数

​ 因为11−x=1+x+x2+...+xn\frac{1}{1-x}=1+x+x^2+...+x^n很容易想到

  1. 二项式展开:(a+b)n=∑k=0n(nk)an−kbk(a+b)^n=\sum_{k=0}^n\binom{n}{k}a^{n-k}b^k
  2. 负二项式展开:(1−x)−n=∑k=0∞(n+k−1k)xk(∣x∣<1)(1-x)^{-n}=\sum_{k=0}^{\infty}\binom{n+k-1}{k}x^k(|x| \lt 1)
  3. 广义形式:(−nk)=(−1)k(n+k−1k)\binom{-n}{k}=(-1)^k\binom{n+k-1}{k},因此(a+b)−n=∑k=0∞(−1)k(n+k−1k)a−n−kbk(a+b)^{-n}=\sum_{k=0}^{\infty}(-1)^k\binom{n+k-1}{k}a^{-n-k}b^k

泰勒展开 ​

普通生成函数(OCF) ​

  1. 11−x=1+x+x2+x3+...=∑n=0∞xn(an=1)\frac{1}{1-x}=1+x+x^2+x^3+...=\sum_{n=0}^{\infty}x^n(a_n=1)(几何级数)
  2. 11−x2=1+x2+x4+...\frac{1}{1-x^2}=1+x^2+x^4+...
  3. 11−x3=1+x3+x6+...\frac{1}{1-x^3}=1+x^3+x^6+...
  4. 1(1−x)2=1+2x+3x2+...\frac{1}{(1-x)^2}=1+2x+3x^2+...
  5. x(1−x)2=0+x+2x2+...(an=n)\frac{x}{(1-x)^2}=0+x+2x^2+...(a_n=n)(自然数序列)
  6. x(1+x)(1−x)3=0+x+4x2+9x3+...(an=n2)\frac{x(1+x)}{(1-x)^3}=0+x+4x^2+9x^3+...(a_n=n^2)(平方数序列)
  7. (1+x)n=∑k=0n(nk)xk(ak=(nk))(1+x)^n=\sum_{k=0}^n\binom{n}{k}x^k(a_k=\binom{n}{k})(二项式系数)
  8. 1(1−k)n=∑n=0∞(n+k−1k−1)xn(an=允许重复的选法)\frac{1}{(1-k)^n}=\sum_{n=0}^{\infty}\binom{n+k-1}{k-1}x^n(a_n=允许重复的选法)
  9. x1−x−x2=0+x+x2+2x3+3x4+...(F0=0,F1=1)\frac{x}{1-x-x^2}=0+x+x^2+2x^3+3x^4+...(F_0=0,F_1=1)(斐波那契数列)
  10. −ln⁡(1−x)1−x=∑n=1∞Hnxn(Hn=1+12+...+1n)\frac{-\ln(1-x)}{1-x}=\sum_{n=1}^{\infty}H_nx^n(H_n=1+\frac{1}{2}+...+\frac{1}{n})(调和数)

指数生成函数(EGF) ​

  1. ex=∑n=0∞xnn!e^x=\sum_{n=0}^{\infty}\frac{x^n}{n!}
  2. e−x=1−x11+x22!−x33!+...e^{-x}=1-\frac{x^1}{1}+\frac{x^2}{2!}-\frac{x^3}{3!}+...
  3. ex+e−x2=1+x22!+x44!+...\frac{e^x+e^{-x}}{2}=1+\frac{x^2}{2!}+\frac{x^4}{4!}+...
  4. ex−e−x2=x+x33!+x55!+...\frac{e^x-e^{-x}}{2}=x+\frac{x^3}{3!}+\frac{x^5}{5!}+...
  5. 11−x=∑n=0∞n!⋅xnn!(an=n!)\frac{1}{1-x}=\sum_{n=0}^{\infty}n! \cdot \frac{x^n}{n!}(a_n=n!)(排列数)
  6. −ln(1−x)=∑n=1∞(n−1)!xnn!(an=(n−1)!)-ln(1-x)=\sum_{n=1}^{\infty}\frac{(n-1)!x^n}{n!}(a_n=(n-1)!)(循环排列数)
  7. eex−1=∑n=0∞Bn⋅xnn!(Bn=贝尔数)e^{e^x-1}=\sum_{n=0}^{\infty}B_n \cdot \frac{x^n}{n!}(B_n=贝尔数)(贝尔数集合划分)
  8. (ex−1)kk!=∑n=k∞S(n,k)⋅xnn!(S(n,k)=第二类斯特林数)\frac{(e^x-1)^k}{k!}=\sum_{n=k}^{\infty}S(n,k)\cdot \frac{x^n}{n!}(S(n,k)=第二类斯特林数)
  9. sin⁡(x)=∑n=0∞(−1)nx2n+1(2n+1)!\sin(x)=\sum_{n=0}^{\infty}(-1)^n\frac{x^{2n+1}}{(2n+1)!}
  10. cos⁡(x)=∑n=0∞(−1)nx2n(2n)!\cos(x)=\sum_{n=0}^{\infty}(-1)^n\frac{x^{2n}}{(2n)!}

有穷序列的生成函数 ​

  1. 1+x+x2=1−x31−x1+x+x^2=\frac{1-x^3}{1-x}
  2. 1+x+x2+x3=1−x41−x1+x+x^2+x^3=\frac{1-x^4}{1-x}
  3. 1+x2+x4+x2m=1−x2(m+1)1−x21+x^2+x^4+x^{2m}=\frac{1-x^{2(m+1)}}{1-x^2}

P10780 BZOJ3028 食物 ​

题目描述 ​

明明这次又要出去旅游了,和上次不同的是,他这次要去宇宙探险!我们暂且不讨论他有多么 NC,他又幻想了他应该带一些什么东西。理所当然的,你当然要帮他计算携带 nn 件物品的方案数。

他这次又准备带一些受欢迎的食物,如:蜜桃多啦,鸡块啦,承德汉堡等等。

当然,他又有一些稀奇古怪的限制:

每种食物的限制如下:

  • 承德汉堡:偶数个;
  • 可乐:00 个或 11 个;
  • 鸡腿:00 个,11 个或 22 个;
  • 蜜桃多:奇数个;
  • 鸡块:44 的倍数个;
  • 包子:00 个,11 个,22 个或 33 个;
  • 土豆片炒肉:不超过一个;
  • 面包:33 的倍数个;

注意,这里我们懒得考虑明明对于带的食物该怎么搭配着吃,也认为每种食物都是以『个』为单位(反正是幻想嘛),只要总数加起来是 nn 就算一种方案。因此,对于给出的 nn,你需要计算出方案数,并对 1000710007 取模。

提示 ​

  • 对于 40%40\% 的数据,但是 1≤n≤1051\leq n\leq 10^5;
  • 对于所有数据,1≤n≤105001\leq n\leq 10^{500};

思路 ​

  • 承德汉堡的生成函数:1+x2+x4+...+xn=11−x21+x^2+x^4+...+x^n=\frac{1}{1-x^2}
  • 可乐为:1+x=1−x21−x1+x=\frac{1-x^2}{1-x}
  • 鸡腿为:1+x+x2=1−x31−x1+x+x^2=\frac{1-x^3}{1-x}
  • 蜜桃为:x+x3+x5+...+xn=x1−x2x+x^3+x^5+...+x^n=\frac{x}{1-x^2}
  • 鸡块为:1+x4+x8+...xn=11−x41+x^4+x^8+...x^n=\frac{1}{1-x^4}
  • 包子为:1+x+x2+x3+x4=1−x41−x1+x+x^2+x^3+x^4=\frac{1-x^4}{1-x}
  • 土豆片炒肉为:1+x=1−x21−x1+x=\frac{1-x^2}{1-x}
  • 面包为:1+x3+x6+...=11−x31+x^3+x^6+...=\frac{1}{1-x^3}
  • 全部相乘得到 f(x)=x(1−x)4f(x)=\frac{x}{(1-x)^4}
  • 将式子展开,得到的 xnx^n 的系数即为方案数
  • f(x)=x(∑n=0∞Cn+4−14−1xn)=∑n=0∞Cn+33xn+1f(x)=x(\sum _{n=0}^{∞}C_{n+4-1}^{4-1}x^n)= \sum _{n=0}^{∞}C_{n+3}^{3}x^{n+1}
  • 答案即为 Cn+23=(n+2)(n+1)n6C_{n+2}^3=\frac{(n+2)(n+1)n}{6}

多重集组合数问题 ​

有nn种物品,每种物品有aia_i个,问取mm个物品的组合数?

那么构造nn个生成函数,第ii个为1+x1+x2+...+xai1+x^1+x^2+...+x^{a_i}

所有函数相乘,指数为mm的系数即为答案

abc248c dice_sum ​

给定三个整数N,M,K,求多少种序列:

  1. A长度为N
  2. 1≤Ai≤M(1≤i≤N)1 \leq A_i \leq M(1 \leq i \leq N)
  3. ∑i=1NAi≤K\sum_{i=1}^NA_i \leq K

f(x)=x+x2+...+xM=x1−xM1−xf(x)=x+x^2+...+x^M=x\frac{1-x^M}{1-x}

那么要求的是f(x)Nf(x)^N的前k项和,转化为求f(x)N1−x\frac{f(x)^N}{1-x}的xKx^K项

f(x)N1−x=xN(1−xM)N(1−x)−(N+1)\frac{f(x)^N}{1-x}=x^N(1-x^M)^N(1-x)^{-(N+1)}

实际上求(1−xM)N(1−x)−(N+1)(1-x^M)^N(1-x)^{-(N+1)}的xK−Nx^{K-N}项

(1−xM)N=∑k=0N(Nk)(−1)kxMk(1−x)−(N+1)=∑k=0∞(−(N+1)k)(−1)kxk=∑(N+kk)(−1)2kxk=∑(N+kk)xk((1−xM)N(1−x)−(N+1))[xK−N]=∑i=0(Ni)(−1)i(N+(K−N−iM)K−N−iM)(1-x^M)^N=\sum_{k=0}^N\binom{N}{k}(-1)^kx^{Mk}\\ (1-x)^{-(N+1)}=\sum_{k=0}^{\infty}\binom{-(N+1)}{k}(-1)^{k}x^k=\sum\binom{N+k}{k}(-1)^{2k}x^k=\sum\binom{N+k}{k}x^k\\ ((1-x^M)^N(1-x)^{-(N+1)})[x^{K-N}]=\sum_{i=0}\binom{N}{i}(-1)^i\binom{N+(K-N-iM)}{K-N-iM}

cpp
void solve(){
    init();
    int n,m,k;
    cin>>n>>m>>k;
    int ans=0;
    for(int i=0;i*m<=k-n;i++){
        int tmp=C(n,i)*C(k-i*m,k-n-i*m)%mod;
        if(i&1)tmp*=-1;
        ans=(ans+tmp+mod)%mod;
    }
    cout<<ans<<"\n";
}

更无脑的方法是直接分治ntt,复杂度O(mlog⁡mlog⁡n)O(m\log m \log n),如果是xN(1−xM)N(1−x)−(N+1)x^N(1-x^M)^N(1-x)^{-(N+1)},那么可以快速幂+多项式求逆+ntt,复杂度(O(mlog⁡m))(O(m\log m))(过于抽象没有实现)

指数生成函数 ​

F(x)=∑n≥0anxnn!F(x)=\sum_{n \geq 0}a_n\frac{x^n}{n!}

序列<1,1,1,...><1,1,1,...>的指数生成函数为1+x1!+x22!+...=∑xnn!=ex1+\frac{x}{1!}+\frac{x^2}{2!}+...=\sum\frac{x^n}{n!}=e^x

序列<1,p,p2,..><1,p,p^2,..>的指数生成函数为∑pnxnn!=epx\sum p^n \frac{x^n}{n!}=e^{px}

多重集排列数问题 ​

有nn个物品,每种物品aia_i个,问mm个物品的排列数

那么答案就是(1+x11+x22+...xaiai!)(1+\frac{x^1}{1}+\frac{x^2}{2}+...\frac{x^{a_i}}{a_i!})所有的和,xmm!\frac{x^m}{m!}的系数

POJ3734 Blocks ​

长度nn的序列,四种颜色,红色和绿色数量必须为偶数,求方案数

红色和绿色:1+x22!+x44!+...=ex+e−x21+\frac{x^2}{2!}+\frac{x^4}{4!}+...=\frac{e^x+e^{-x}}{2}

蓝色黄色:1+x1!+x22!+...=ex1+\frac{x}{1!}+\frac{x^2}{2!}+...=e^x

(ex+e−x2)2e2x=e4x+2e2x+14(\frac{e^x+e^{-x}}{2})^2e^{2x}=\frac{e^{4x}+2e^{2x}+1}{4}

因为∑i=0kixii!=ekx\sum_{i=0}\frac{k^ix^i}{i!}=e^{kx}

因此xnx^n系数为4n+2⋅2n4n!\frac{4^n+2\cdot 2^n}{4n!},xnn!\frac{x^n}{n!}系数为4n+2⋅2n4\frac{4^n+2\cdot 2^n}{4}

牛客17316(挑选队友)(分治FFT) ​

共有nn位选手,mm个群,kk名选手被选择,问方案数

sis_i表示该群的人数,可以发现该群的xkx^k的多项式系数为CsikC_{s_i}^k,mm个数卷积即可

使用分治fft

T(n)=2T(n2)+O(nlog⁡n)T(n)=2T(\frac{n}{2})+O(n\log n),复杂度为O(nlog⁡2n)O(n \log^2 n)

cpp
void solve(){
    int n,m,k;
    cin>>n>>m>>k;
    comb.init(n);
    vector<int> s(n+1);
    for(int i=1;i<=m;i++)cin>>s[i];
    auto dfs=[&](auto &&dfs,int l,int r){
        if(l==r){
            poly f(s[l]+1);
            for(int i=1;i<=s[l];i++)f[i]=comb.binom(s[l],i);
            return f;
        }
        int mid=l+r>>1;
        return mul(dfs(dfs,l,mid),dfs(dfs,mid+1,r));
    };
    auto res=dfs(dfs,1,m);
    cout<<res[k]<<"\n";
}

注意mul函数处的init(n+m+1)改成init(n+m),否则复杂度是错的

abc234f Reordering(指数型) ​

给定字符串SS,求他的所有排列能生成的子序列个数

26个字母,每个字母出现次数为aia_i个,该字母的生成函数为1+x+x22+...+xaiai!1+x+\frac{x^2}{2}+...+\frac{x^{a_i}}{a_i!}

所有26个字母相乘

答案为∑i=1ni!⋅fi\sum_{i=1}^{n}i!\cdot f_i

cpp
void solve(){
    string s;
    cin>>s;
    int n=s.size();
    comb.init(n);
    poly res{1};
    vector<int> a(26);
    for(auto c:s)a[c-'a']++;
    for(int i=0;i<26;i++){
        if(!a[i])continue;
        poly tmp(a[i]+1,1);
        for(int j=1;j<=a[i];j++)tmp[j]=comb.inv[j];
        res.resize(n+1);
        res=mul(res,tmp);
    }
    int ans=0;
    for(int i=1;i<=n;i++)ans=(ans+comb.fac[i]*res[i])%mod;
    cout<<ans<<"\n";
}

CF632F ​

nn种物品,价值为aia_i,求拿走恰好kk种物品的所有可能价值,1≤n,ai,k≤10001 \leq n,a_i,k \leq 1000

定义fi,jf_{i,j}表示选择了ii个物品,价值为jj的方案数,fi,j=min⁡fi−1,j−akf_{i,j}=\min f_{i-1,j-a_k}转移O(n4)O(n^4)

换个思路,定义fif_i表示价值为ii的最小物品数

假设价值最小值为xx,那么所有物品ai=ai−xa_i=a_i-x,那么此时即使fi<kf_i \lt k,可以添加价值为0的物品数,答案就是i+xki+xk

另一种思路:

定义gig_i为价值的出现次数,g(x)=1+b1x1+b2x2+...g(x)=1+b_1x^1+b_2x^2+...

那么fi,j=fi−1,j−k∗gkf_{i,j}=f_{i-1,j-k}*g_k是一个卷积形式,NTT求解(不卡模数的情况下)

CF1096G ​

一个nn位数,每位可以是给出的kk个数码的一个数,可以有前导00,输出前n2\frac{n}{2}和后n2\frac{n}{2}相等的方案数

设出现的数位集合为SS,那么生成函数F(X)=∑i∈SxiF(X)=\sum_{i \in S}x^i,答案为∑[xi]Fn2(x)\sum [x^i]F^{\frac{n}{2}}(x)

GYM102920H ​

三条水平线a,b,c,每个水平线有nn个点,高度相差1,求三点共线数量。

ai+cj=2bka_i+c_j=2b_k,构造生成函数,F(x)=∑i∈SxiF(x)=\sum_{i \in S}x^i,SS是值的集合,答案就是A(x)C(x)A(x)C(x)中x2kx^{2k}的系数,前提bk=1b_k=1

做一遍FFT即可

HDU7328 Snake ​

有nn个长度为1的蛇,求集合个数(比如n=2n=2集合有{1->2},{2->1},{1,2},{2,1},最终有mm条蛇,每条蛇长度最多为kk

容斥做法:

考虑没有排列的因素,f(i)f(i)表示至少ii条蛇大于kk的方案

ans=f0−f1+f2....ans=f_0-f_1+f_2....

  • 对于ii,选出ii个集合等于kk的,(mi)\binom{m}{i}
  • 剩余n−ikn-ik分成mm份,(n−ik−1m−1)\binom{n-ik-1}{m-1}

考虑排序,ans⋅n!m!\frac{ans \cdot n!}{m!}全排列,但是集合不排

cpp
void solve(){
    int n,m,k;
    cin>>n>>m>>k;
    vector<int> f(n+1);
    for(int i=0;i<=m;i++){
        f[i]=comb.binom(m,i)*comb.binom(n-i*k-1,m-1)%mod;
    }
    int ans=0;
    for(int i=0;i<=m;i++){
        if(i&1)ans=(ans+mod-f[i])%mod;
        else ans=(ans+f[i])%mod;
    }
    cout<<ans*comb.fac[n]%mod*comb.inv[m]%mod<<"\n";
}

生成函数做法:

每个集合的生成函数F(x)=x1+x2+...+xk=x1−xk1−xF(x)=x^1+x^2+...+x^k=x \frac{1-x^k}{1-x}

答案就是Fm(x)[xn]F^m(x)[x^n],化简和abc248c一样

最后答案为ans⋅n!m!\frac{ans \cdot n!}{m!}

2017CCPC 杭州Marriage(容斥+生成函数) ​

有nn个家庭,每个家庭有aia_i个男孩和bib_i个女孩,∑ai=∑bi\sum a_i=\sum b_i,除了同家庭,其余男女配对,问方案数

考虑容斥,fif_i表示至少kk对同家庭的

设p=min⁡(ai,bi)p=\min(a_i,b_i)为当前家庭的贡献,设贡献ii对,方案为ci=(ai)(bi)i!c_i=\binom{a}{i} \binom{b}{i}i!,生成函数为∑i=0pcixi\sum_{i=0}^p c_ix^i

那么所有生成函数相乘,fi=[xi]f_i=[x^i]

容斥一下计算答案即可

复杂度O(nlog⁡2n)O(n \log^2n)

分治NTT需要用优先队列启发式合并实现

cpp
void solve(){
    int n;
    cin>>n;
    vector<int> a(n+1),b(n+1);
    for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
    int s=accumulate(a.begin()+1,a.end(),0LL);
    int t=0;
    for(int i=1;i<=n;i++)t+=min(a[i],b[i]);
    vector<poly> f(n+1);
    for(int i=1;i<=n;i++){
        int p=min(a[i],b[i]);
        f[i].resize(p+1);
        for(int j=0;j<=p;j++)f[i][j]=comb.binom(a[i],j)*comb.binom(b[i],j)%mod*comb.fac[j]%mod;
    }
    auto cmp=[&](int x,int y){
        return f[x].size()>f[y].size();
    };
    priority_queue<int,vector<int>,decltype(cmp)> pq(cmp);
    for(int i=1;i<=n;i++)pq.push(i);
    while(pq.size()>1){
        auto x=pq.top();pq.pop();
        auto y=pq.top();pq.pop();
        int z=f.size();
        auto g=mul(f[x],f[y]);
        g.resize(min((int)g.size(),t+1));
        f.push_back(g);
        pq.push(z);
    }
    auto F=f.back();
    int ans=0;
    for(int i=0;i<=t;i++){
        int tmp=F[i]*comb.fac[s-i]%mod;
        if(i&1)tmp=mod-tmp;
        ans=(ans+tmp)%mod;
    }
    cout<<ans<<'\n';
}

CCPC桂林B ​

求Ai=(∑j=imin⁡(i+L−1,n)Aj)mod  PA_i=(\sum_{j=i}^{\min(i+L-1,n)}A_j) \mod P,执行mm次

将数组AA倒序,构造数组G(x)=x0+x1+x2+...+xLG(x)=x^0+x^1+x^2+...+x^L

每执行一次操作即A(x)∗G(x)A(x)*G(x)

答案是A(x)∗Gm(x)A(x)*G^m(x)

cpp
void solve(){
    int _=0;
    cin>>_;
    for(int T=1;T<=_;T++){
        int n,L,m;
        cin>>n>>L>>m;
        poly f(n);
        for(int i=0;i<n;i++)cin>>f[i];
        ranges::reverse(f);
        poly g(L,1);
        g=qpow(g,m,n);
        
        f=mul(f,g);
        f.resize(n);
        cout<<"Case "<<T<<": ";
        for(int i=n-1;i>=0;i--)cout<<f[i]<<" ";
        cout<<"\n";
    }
}

2018沈阳M ​

多重背包问题,每个位置有数量为aa价值为bb的物品

多次在线询问,求[L,R][L,R]选够价值cc个物品的方案

每个位置的生成函数为1+xb+x2b+...+xab=1−x(a+1)b1−xb1+x^b+x^{2b}+...+x^{ab}=\frac{1-x^{(a+1)b}}{1-x^b}

答案为∏i=lr1−x(a+1)b1−xb=∏i=1rx(a+1)b−1xb−1∏i=1l−1xb−1x(a+1)b−1\prod_{i=l}^r\frac{1-x^{(a+1)b}}{1-x^b}=\prod_{i=1}^r\frac{x^{(a+1)b}-1}{x^b-1}\prod_{i=1}^{l-1}\frac{x^b-1}{x^{(a+1)b}-1}

cpp
void solve(){
    int _;
    cin>>_;
    for(int T=1;T<=_;T++){
        cout<<"Case #"<<T<<":\n";
        int n,m;
        cin>>n>>m;
        const int M=2001;
        vector<int> f(M),g(M);
        vector sf(n+1,vector<int>(M)),sg(n+1,vector<int>(M));
        f[0]=g[0]=sf[0][0]=sg[0][0]=1;
        for(int i=1;i<M;i++)sf[0][i]=1;
        auto _01=[&](vector<int> &dp,int x){
            for(int i=M-1;i>=x;i--)dp[i]=(dp[i]-dp[i-x]+mod)%mod;
        };
        auto _complete=[&](vector<int> &dp,int x){
            for(int i=x;i+x<M;i++)dp[i]=(dp[i]+dp[i-x])%mod;
        };
        for(int i=1;i<=n;i++){
            int a,b;
            cin>>a>>b;
            _01(f,(a+1)*b);
            _01(g,b);
            _complete(f,b);
            _complete(g,(a+1)*b);
            sf[i][0]=f[0];
            sg[i][0]=g[0];
            for(int j=1;j<M;j++){
                sf[i][j]=(sf[i][j-1]+f[j])%mod;
                sg[i][j]=g[j];
            }
        }
        int ans=0;
        while(m--){
            int l,r,c;
            cin>>l>>r>>c;
            l=(l+ans)%n+1,r=(r+ans)%n+1;
            if(l>r)swap(l,r);
            ans=0;
            for(int i=0;i<=c;i++)ans=(ans+sf[r][i]*sg[l-1][c-i]%mod)%mod;
            cout<<ans<<"\n";
        }
    }
}

2020ICPC澳门 ​

在这里插入图片描述在这里插入图片描述