题解 nflsoj550 【六校联合训练 省选 #9】序列

大憨熊 提交于 2020-02-27 15:01:27

题目链接

以下把值域(题面里的\(lim\))记做\(m\)

考虑求\(k\)的答案。考虑每个位置对答案的贡献,枚举位置\(i\),再枚举\(a[i]\)的值\(x\)。设:
\[ F(k)=\sum_{i=k+1}^{n}m^{i-k-1}m^{n-i}{i-1\choose k}\sum_{x=1}^{m}(x-1)^k \]
如果对着式子打一个\(O(n^2m)\)的暴力,就会发现这个\(F(k)\)并不是答案,它比答案大。它其实是\(i\)前面至少\(k\)个数的方案数。而一个序列,如果\(i\)前面有超过\(k\)\(\leq x\)的数,比方说有\(j\)个,那这个序列就会被计算\(j\choose k\)次。形式化地说,如果设\(i\)前面恰好\(k\)\(\leq x\)数的方案数为\(G(k)\),则:
\[ F(k)=\sum_{i=k}^{n}{i\choose k}G(i) \]
而这个\(G(k)\)的后缀和,就是我们要求的答案。

根据二项式反演,可知:
\[ G(k)=\sum_{i=k}^{n}(-1)^{i-k}{i\choose k}F(i) \]
如果知道了\(F[1\dots n]\),就可以用NTT快速求出\(G[1\dots n]\),进而求出答案。这部分我们稍后再说,先看如何求\(F\)

\(S(m,k)=\sum_{i=0}^{m-1}i^k\)。则:
\[ \begin{align} F(k)=&m^{n-k-1}S(m,k)\sum_{i=k+1}^{n}{i-1\choose k}\\ =&m^{n-k-1}S(m,k)\sum_{i=k}^{n-1}{i\choose k}\\ =&m^{n-k-1}S(m,k){n\choose k+1} \end{align} \]
最后一步相当于求杨辉三角上连续若干行,每行的第\(k\)个数的和(画出来是一条斜向左下的斜线)。可以从上到下把每两个数合并为下一行的第\(k+1\)个数。因此\(\sum_{i=k}^{n-1}{i\choose k}={n\choose k+1}\)

现在的难点是求出对所有\(k\in[1,n]\),求出\(S(m,k)\)。构造\(S(m,k)\)的指数生成函数\(H(x)=\sum_{i=0}^{\infty}S(m,i)\frac{x^i}{i!}\),则:
\[ \begin{align} H(x)&=\sum_{i=0}^{\infty}\frac{x^i}{i!}\sum_{j=0}^{m-1}j^i\\ &=\sum_{j=0}^{m-1}\sum_{i=0}^{\infty}\frac{x^ij^i}{i!} \end{align} \]
根据小学二年级学过的泰勒展开,我们知道,\(e^x=\sum_{i=0}^{\infty}\frac{x^i}{i!}\)。把这里的\(x\)替换为\((jx)\),则:
\[ \begin{align} H(x)&=\sum_{j=0}^{m-1}e^{jx}\\ &=\frac{e^{mx}-1}{e^x-1} \end{align} \]
注:这一步变换是等比数列求和,即把式子整体乘以\(e^x\)再用新式子减去原式。

再次感谢泰勒展开,我们知道分子和分母其实分别是两个无限长的数列。我们要求是\(H(x)\)的前\(n\)项的值。这个值显然只和两个数列的前\(n\)项有关,因此取出分母的前\(n\)项,做多项式求逆,再用NTT把两个数列乘起来即可。分母的常数项是\(0\)不好处理,但注意到分子的常数项也是\(0\),我们只要把分子分母同时除以\(x\)就行,即两个数列分别左移一位就行。

这样,我们就求出了\(F[0\dots n-1]\),回顾开头部分的讨论,我们要求出:\(G(k)=\sum_{i=k}^{n-1}(-1)^{i-k}{i\choose k}F(i)\quad k\in[0,n-1]\)

把组合数拆开,得到:
\[ G(k)=\sum_{i=k}^{n-1}(-1)^{i-k}\frac{i!}{k!(i-k)!}F(i) \]
先把\(k!\)拎出来。然后设\(f_i=i!F(i),g_i=(-1)^i\frac{1}{i!}\),则:
\[ G(k)=k!\sum_{i=k}^{n-1}f_ig_{i-k} \]
我们熟悉的卷积都是\(c_k=\sum_{i=0}^{k}a_ib_{k-i}\),而这里的形式是\(c_k=\sum_{i=k}^{n}f_ig_{i-k}\),考虑如何转化。注:这里为方便表述,我们令\(n=n-1\)

仔细考虑卷积的本质,我们的目标是要让相乘的两个数下标之和为定值,而我们能做的事就是尝试翻转一个数列。不妨尝试翻转\(f\),即令\(f_i=f_{n-i}\)

此时\(c_k=\sum_{i=k}^{n}f_{n-i}g_{i-k}\)

\(i\)换成\(i-k\),则\(c_k=\sum_{i=0}^{n-k}f_{n-k-i}g_{i}\)。发现\(f\)\(g\)的下标和为\(n-k\),刚好是一个定值。此时如果把\(f\)\(g\)卷起来,那么结果的第\(n-k\)项就是\(c_k\)。再乘上\(k!\)就是\(G(k)\)了。

求出\(G\)后,做后缀和,得到答案。

时间复杂度\(O(n\log n)\),共做了一次多项式求逆,两次多项式乘法。

参考代码:

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

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

/*  ------  by:duyi  ------  */ // dysyn1314
const int MAXN=114514,MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int n,m,fac[MAXN+5],invf[MAXN+5];
void init(){
    fac[0]=1;
    for(int i=1;i<=MAXN;++i)fac[i]=(ll)fac[i-1]*i%MOD;
    invf[MAXN]=pow_mod(fac[MAXN],MOD-2);
    for(int i=MAXN-1;i>=0;--i)invf[i]=(ll)invf[i+1]*(i+1)%MOD;
}
inline int comb(int n,int k){
    if(n<k)return 0;
    return (ll)fac[n]*invf[k]%MOD*invf[n-k]%MOD;
}
int f[MAXN*4+5],g[MAXN*4+5],h[MAXN*4+5],rev[MAXN*4+5];
void NTT(int *a,int n,int flag){
    for(int i=0;i<n;++i)if(i<rev[i])swap(a[i],a[rev[i]]);
    for(int i=1;i<n;i<<=1){
        int T=pow_mod(3,(MOD-1)/(i<<1));
        if(flag==-1) T=pow_mod(T,MOD-2);
        for(int j=0;j<n;j+=(i<<1)){
            for(int k=0,t=1;k<i;++k,t=(ll)t*T%MOD){
                int Nx=a[j+k],Ny=(ll)a[i+j+k]*t%MOD;
                a[j+k]=mod1(Nx+Ny);
                a[i+j+k]=mod2(Nx-Ny);
            }
        }
    }
    if(flag==-1){
        int invn=pow_mod(n,MOD-2);
        for(int i=0;i<n;++i)a[i]=(ll)a[i]*invn%MOD;
    }
}
void mul(int *a,int *b,int n,int m){
    // b <- a*b
    static int f[MAXN*4+5],g[MAXN*4+5];
    for(int i=0;i<n;++i)f[i]=a[i];
    for(int i=0;i<m;++i)g[i]=b[i];
    int lim=1,ct=0;
    while(lim<=n+m)lim<<=1,ct++;
    for(int i=n;i<lim;++i)f[i]=0;
    for(int i=m;i<lim;++i)g[i]=0;
    for(int i=0;i<lim;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(ct-1));
    NTT(f,lim,1);
    NTT(g,lim,1);
    for(int i=0;i<lim;++i)f[i]=(ll)f[i]*g[i]%MOD;
    NTT(f,lim,-1);
    for(int i=0;i<n;++i)b[i]=f[i];
}
void _mul(int *a,int *b,int n,int m){
    // b <- 2b-a*b^2
    static int f[MAXN*4+5],g[MAXN*4+5];
    for(int i=0;i<n;++i)f[i]=a[i];
    for(int i=0;i<m;++i)g[i]=b[i];
    int lim=1,ct=0;
    while(lim<=(n*2))lim<<=1,ct++;
    for(int i=n;i<lim;++i)f[i]=0;
    for(int i=m;i<lim;++i)g[i]=0;
    for(int i=0;i<lim;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(ct-1));
    NTT(f,lim,1);
    NTT(g,lim,1);
    for(int i=0;i<lim;++i)f[i]=mod2(mod1(g[i]*2)-(ll)f[i]*g[i]%MOD*g[i]%MOD);
    NTT(f,lim,-1);
    for(int i=0;i<n;++i)b[i]=f[i];
}
void get_inv(int *a,int n,int *res){
    if(n==1){
        res[0]=pow_mod(a[0],MOD-2);
        return;
    }
    int m=(n+1)>>1;
    get_inv(a,m,res);
    _mul(a,res,n,m);
}
int main() {
    init();
    scanf("%d%d",&n,&m);m%=MOD;
    for(int i=0,pw=1;i<n;++i){
        pw=(ll)pw*m%MOD;
        f[i]=(ll)pw*invf[i+1]%MOD;
        g[i]=invf[i+1];
    }
    get_inv(g,n,h);//h=g^{-1}
    mul(f,h,n,n);//h=f*h
    for(int i=0;i<n;++i)h[i]=(ll)h[i]*fac[i]%MOD;
    //for(int i=0;i<n;++i)cout<<h[i]<<" ";cout<<endl;
    memset(f,0,sizeof(f));
    memset(g,0,sizeof(g));
    for(int k=0;k<n;++k)f[k]=(ll)pow_mod(m,n-k-1)*h[k]%MOD*comb(n,k+1)%MOD;//f[k]至少选k个的贡献
    //f[k] -> g[k](恰好选k个的贡献)
    for(int i=0;i<n;++i)f[i]=(ll)f[i]*fac[i]%MOD;
    for(int i=0;i<n;++i)h[i]=f[n-1-i];
    for(int i=0;i<n;++i)f[i]=h[i];
    for(int i=0;i<n;++i)if(i&1)g[i]=mod2(-invf[i]);else g[i]=invf[i];
    mul(f,g,n,n);
    for(int i=0;i<n;++i)h[i]=g[n-1-i];
    for(int i=0;i<n;++i)g[i]=(ll)h[i]*invf[i]%MOD;g[n]=0;
    for(int i=n-1;i>=1;--i)add(g[i],g[i+1]);
    for(int i=1;i<=n;++i)printf("%d ",g[i]);puts("");
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!