【BZOJ】4555: [Tjoi2016&Heoi2016]求和 排列组合+多项式求逆 或 斯特林数+NTT

【题意】给定n,求Σi=0~nΣj=1~i s(i,j)*2^j*j!,n<=10^5。

【算法】生成函数+排列组合+多项式求逆

【题解】参考: [BZOJ4555][Tjoi2016&Heoi2016]求和-NTT-多项式求逆

$ans=\sum_{i=0}^{n}\sum_{j=0}^{i}s(i,j)*2^j*j!$

令$g(n)=\sum_{j=0}^{n}s(n,j)*2^j*j!$

则ans是Σg(i),只要计算出g(i)的生成函数就可以统计答案。

g(n)可以理解为将n个数划分成若干集合,每个集合有2个属性的排列数。基于此实际意义,通过枚举第一个集合来递推g(n)。

$g(n)=\sum_{i=1}^{n}2*C(n,i)*g(n-i)$

特别的,g(0)=1

两边乘n!(令人窒息的操作),得

$\frac{g(n)}{n!}=\sum_{i=1}^{n}\frac{2}{i!}*\frac{g(n-i)}{(n-i)!}$

这已经是卷积的形式了:

$F(n)=\sum_{i=1}^{n}\frac{2}{n!}*x^i$

$G(n)=\sum_{i=0}^{n}\frac{g(n)}{n!}*x^i$

注意此时F*G卷积后,G(0)的位置是0,所以

$G(n)=F(n)G(n)+1$

移项得,

$G(n)=\frac{1}{1-F(n)}$

最后,多项式求逆即可,复杂度O(n log n)。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=300010,MOD=998244353;
void gcd(int a,int b,int &x,int &y){
    if(!b){x=1;y=0;}else{gcd(b,a%b,y,x);y-=x*(a/b);}
}
int inv(int a){int x,y;gcd(a,MOD,x,y);return (x%MOD+MOD)%MOD;}
int power(int x,int k){
    int ans=1;
    while(k){
        if(k&1)ans=1ll*ans*x%MOD;
        x=1ll*x*x%MOD;
        k>>=1;
    }
    return ans;
}
namespace ntt{
    int o[maxn],oi[maxn],f[maxn];
    void init(int n){
        int x=1,p=power(3,(MOD-1)/n);
        for(int k=0;k<n;k++){
            o[k]=x;oi[k]=inv(o[k]);
            x=1ll*x*p%MOD;
        }
    }
    void transform(int *a,int n,int *o){
        int k=0;
        while((1<<k)<n)k++;
        for(int i=0;i<n;i++){
            int t=0;
            for(int j=0;j<k;j++)if((1<<j)&i)t|=(1<<(k-j-1));
            if(i<t)swap(a[i],a[t]);
        }
        for(int l=2;l<=n;l*=2){
            int m=l>>1;
            for(int *p=a;p!=a+n;p+=l){
                for(int i=0;i<m;i++){
                    int t=1ll*p[i+m]*o[n/l*i]%MOD;
                    p[i+m]=(p[i]-t+MOD)%MOD;
                    p[i]=(p[i]+t)%MOD;
                }
            }
        }
    }
    void dft(int *a,int n){transform(a,n,o);}
    void idft(int *a,int n){
        transform(a,n,oi);int v=inv(n);
        for(int i=0;i<n;i++)a[i]=1ll*a[i]*v%MOD;
    }
    void pinv(int *F,int *g,int n){
        if(n==1){g[0]=inv(F[0]);return;}
        pinv(F,g,n>>1);n<<=1;
        init(n);//
        for(int i=0;i<n/2;i++)f[i]=F[i],f[i+n/2]=0;
        dft(f,n);dft(g,n);
        for(int i=0;i<n;i++)g[i]=1ll*g[i]*(2-1ll*f[i]*g[i]%MOD+MOD)%MOD;//1ll
        idft(g,n);for(int i=n/2;i<n;i++)g[i]=0;//

    }
}
int F[maxn],G[maxn],n,fac[maxn];
int main(){
    int n,N=1;
    scanf("%d",&n);n++;
    while(N<n)N*=2;
    fac[0]=1;
    for(int i=1;i<n;i++)fac[i]=1ll*fac[i-1]*i%MOD;
    for(int i=1;i<n;i++)F[i]=((-2*inv(fac[i]))%MOD+MOD)%MOD;
    F[0]++;
    ntt::pinv(F,G,N);
    int ans=0;
    for(int i=0;i<n;i++)ans=(ans+1ll*G[i]*fac[i]%MOD)%MOD;
    printf("%d",ans);
    return 0;
}

注意:多项式求逆过程中每次都要对不同的n进行一次预处理omega[]。

另一种做法

【算法】斯特林数+NTT

【题解】首先有第二类斯特林数的通项公式。

$s(n,m)=\frac{1}{m!}\sum_{k=0}^{m}(-1)^k*C(m,k)*(m-k)^n$

当斯特林数s(n,m)满足m>n时,上述公式计算结果为0,所以第二个Σ可以扩展到n。

$ans=\sum_{i=0}^{n}\sum_{j=0}^{n}s(i,j)*s^j*j!$

代入第二类斯特林数公式。

$ans=\sum_{i=0}^{n}\sum_{j=0}^{n}2^j*j!*\frac{1}{j!}\sum_{k=0}^{j}(-1)^k*\frac{j!}{k!(j-k)!}*(j-k)^i$

通过组合数的分解,向卷积靠拢。

注意到Σi只对最后一个括号有影响,所以移动到最后。

$ans=\sum_{j=0}^{n}2^j*j!\sum_{k=0}^{j}\frac{(-1)^k}{k!}*\frac{\sum_{i=0}^{n}(j-k)^i}{(j-k)!}$

这已经是标准的卷积形式(第二个函数分子是等比数列可以直接计算)。

使用NTT计算。

注意:

1.n=0,只有使0^0=1,斯特林数通项公式才能处理s(0,0)的情况。

2.n=1,等比数列求和公式不能处理Σ1^i即q=1的情况。

所以特殊地,g[0]=1,g[1]=n+1。先计算完再n++。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=300010,MOD=998244353;

int power(int x,int k){
    int ans=1;
    while(k){
        if(k&1)ans=1ll*ans*x%MOD;
        x=1ll*x*x%MOD;
        k>>=1;
    }
    return ans;
}
int inv(int x){return power(x,MOD-2);}
namespace ntt{
    int o[maxn],oi[maxn];
    void init(int n){
        int x=1,g=power(3,(MOD-1)/n);
        for(int k=0;k<n;k++){
            o[k]=x;oi[k]=inv(o[k]);
            x=1ll*x*g%MOD;
        }
    }
    void transform(int *a,int n,int *o){
        int k=0;
        while((1<<k)<n)k++;
        for(int i=0;i<n;i++){
            int t=0;
            for(int j=0;j<k;j++)if((1<<j)&i)t|=(1<<(k-j-1));
            if(i<t)swap(a[i],a[t]);
        }
        for(int l=2;l<=n;l*=2){
            int m=l>>1;
            for(int *p=a;p!=a+n;p+=l){
                for(int i=0;i<m;i++){
                    int t=1ll*p[i+m]*o[n/l*i]%MOD;
                    p[i+m]=(p[i]-t+MOD)%MOD;
                    p[i]=(p[i]+t)%MOD;
                }
            }
        }
    }
    void dft(int *a,int n){transform(a,n,o);}
    void idft(int *a,int n){
        transform(a,n,oi);
        int x=inv(n);
        for(int i=0;i<n;i++)a[i]=1ll*a[i]*x%MOD;
    }
}
int n,fac[maxn],f[maxn],g[maxn];
int main(){
    scanf("%d",&n);
    fac[0]=1;
    for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%MOD;
    for(int i=0;i<=n;i++)f[i]=1ll*((i&1)?MOD-1:1)*inv(fac[i])%MOD;
    for(int i=2;i<=n;i++)g[i]=1ll*(1-power(i,n+1)+MOD)*inv((1-i+MOD)%MOD)%MOD*inv(fac[i])%MOD;
    g[0]=1;g[1]=n+1;//
    n++;int N=1;//
    while(N<n+n)N*=2;
    ntt::init(N);
    ntt::dft(f,N);ntt::dft(g,N);
    for(int i=0;i<N;i++)f[i]=1ll*f[i]*g[i]%MOD;
    ntt::idft(f,N);
    int ans=0;
    for(int i=0;i<n;i++)ans=(ans+1ll*power(2,i)*fac[i]%MOD*f[i]%MOD)%MOD;
    printf("%d",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/onioncyc/p/8476913.html

时间: 2024-10-06 00:41:34

【BZOJ】4555: [Tjoi2016&Heoi2016]求和 排列组合+多项式求逆 或 斯特林数+NTT的相关文章

bzoj 4555 [Tjoi2016&amp;Heoi2016]求和 NTT 第二类斯特林数 等比数列求和优化

[Tjoi2016&Heoi2016]求和 Time Limit: 40 Sec  Memory Limit: 128 MBSubmit: 679  Solved: 534[Submit][Status][Discuss] Description 在2016年,佳媛姐姐刚刚学习了第二类斯特林数,非常开心. 现在他想计算这样一个函数的值: S(i, j)表示第二类斯特林数,递推公式为: S(i, j) = j ∗ S(i − 1, j) + S(i − 1, j − 1), 1 <= j &l

4555: [Tjoi2016&amp;Heoi2016]求和

我们省选的题- 考虑这个式子的组合意义,对于每一个i,枚举j表示将i个小球放入j个有序集合,且每个集合选择或者不选的方案数. 我们用f[i]表示将i个小球放入任意个有序集合,且每个集合选择或不选的方案数,则枚举最后一个集合的大小i-j,可以得到递推式: for(int i = 1;i <=n ;i ++) for(int j = 0;j < i ;j ++)f[i]=(f[i]+f[j]*c[i][j]*2); c[i][j]是组合数. 意义就是有j个小球任意组合,剩下的组成最后一个集合,且这

【BZOJ4555】[Tjoi2016&amp;Heoi2016]求和 NTT

[BZOJ4555][Tjoi2016&Heoi2016]求和 Description 在2016年,佳媛姐姐刚刚学习了第二类斯特林数,非常开心. 现在他想计算这样一个函数的值: S(i, j)表示第二类斯特林数,递推公式为: S(i, j) = j ∗ S(i − 1, j) + S(i − 1, j − 1), 1 <= j <= i − 1. 边界条件为:S(i, i) = 1(0 <= i), S(i, 0) = 0(1 <= i) 你能帮帮他吗? Input 输入

hdu 2200 Eddy&#39;s AC难题 (排列组合 就是求(a+b)的n次方的展开式)

Eddy's AC难题 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 3770    Accepted Submission(s): 1765 Problem Description Eddy是个ACMer,他不仅喜欢做ACM题,而且对于Ranklist中每个人的ac数量也有一定的研究,他在无聊时经常在纸上把Ranklist上每个人的

【BZOJ 3456】 3456: 城市规划 (NTT+多项式求逆)

3456: 城市规划 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 658  Solved: 364 Description 刚刚解决完电力网络的问题, 阿狸又被领导的任务给难住了. 刚才说过, 阿狸的国家有n个城市, 现在国家需要在某些城市对之间建立一些贸易路线, 使得整个国家的任意两个城市都直接或间接的连通. 为了省钱, 每两个城市之间最多只能有一条直接的贸易路径. 对于两个建立路线的方案, 如果存在一个城市对, 在两个方案中是否建立路线不一

多项式求逆 学习总结

感觉蒟蒻现在学多项式求逆貌似有些晚了 不过真的很有意思了(然而省选的时候自己还在玩泥巴什么也不会 多项式求逆是基于倍增的 假设我们知道 h(x)f(x)=1(mod x^n) 移项得 (h(x)*f(x)-1)=0(mod x^n) 两边同时求平方得 h(x)^2*f(x)^2 - 2*h(x)*f(x) +1 = 0 (mod x^2n) 设g(x)*f(x)=1(mod x^2n) 两边同时乘以g(x)可以得 h(x)^2*f(x) -2*h(x) + g(x) =0 (mod x^2n)

NTT+多项式求逆+多项式开方(BZOJ3625)

定义多项式h(x)的每一项系数hi,为i在c[1]~c[n]中的出现次数. 定义多项式f(x)的每一项系数fi,为权值为i的方案数. 通过简单的分析我们可以发现:f(x)=2/(sqrt(1-4h(x))+1) 于是我们需要多项式开方和多项式求逆. 多项式求逆: 求B(x),使得A(x)*B(x)=1 (mod x^m) 考虑倍增. 假设我们已知A(x)*B(x)=1 (mod x^m),要求C(x),使得A(x)*C(x)=1 (mod x^(2m)) 简单分析可得C(x)=B(x)*(2-A

Nescafe41 ProblemA 异化多肽 多项式求逆

题目大意:. 思路: 搞出C的生成函数F(x),那么: 长度为1的答案为F(x) 长度为2的答案为F2(x) - 故最终的答案为 F(x)+F2(x)+F3(x)+... =1?F+∞(x)1?F(x) =11?F(x) 然后就是多项式求逆了= = 跪picks大毒瘤 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M (263000&

【bzoj3456】城市规划 容斥原理+NTT+多项式求逆

题目描述 求出n个点的简单(无重边无自环)无向连通图数目mod 1004535809(479 * 2 ^ 21 + 1). 输入 仅一行一个整数n(<=130000) 输出 仅一行一个整数, 为方案数 mod 1004535809. 样例输入 3 样例输出 4 题解 容斥原理+NTT+多项式求逆 设 $f_i$ 表示 $i$ 个点的简单无向连通图的数目,$g_i$ 表示 $i$ 个点的简单无向图的数目. 根据定义得 $g_i=2^{\frac{n(n-1}2}$ . 对于 $f_i$ ,考虑容斥