zoukankan      html  css  js  c++  java
  • 【BZOJ4555】求和(多种解法混合版本)

    【BZOJ4555】求和(多种解法混合版本)

    题面

    BZOJ
    给定(n),求

    [f(n)=sum_{i=0}^{n}sum_{j=0}^{i}S(i,j) imes 2^j imes (j!) ]

    (n<=100000),结果对(998244353)取模。

    其中(S(i,j))是第二类斯特林数,表示将(i)个有区别的球放入(j)个相同的盒子中,每个盒子非空的方案数。

    (S(n,m)=S(n-1,m-1)+S(n-1,m)*m)

    边界条件:(S(i,0)=0,S(i,i)=1)

    题解

    第二类斯特林数考虑组合意义,可以得到另外一个式子

    [S(n,m)=frac{1}{m!}sum_{i=0}^{m}(-1)^iC_m^i(m-i)^n ]

    具体的考虑方法是:先假设盒子不同,然后枚举哪些盒子为空,容斥算出所有盒子都非空的方案数,然后除掉盒子不同产生的多余的贡献。

    将组合数拆开,我们得到

    [S(n,m)=frac{1}{m!}sum_{i=0}^{m}(-1)^ifrac{m !}{i !(m-i)!}(m-i)^n ]

    [S(n,m)=sum_{i=0}^{m}frac{(-1)^i}{i !}frac{(m-i)^n}{(m-i)!} ]

    这很明显是一个卷积的形式,因此,我们可以利用(FFT/NTT)(O(nlogn))的时间内预处理所有的(S(n,i),iin[0,n])

    做法一:第二类斯特林数展开式(容斥原理)

    [f(n)=sum_{i=0}^nsum_{j=0}^nS(i,j)2^jj! ]

    将第二类斯特林数展开为卷积的形式

    [=sum_{i=0}^nsum_{j=0}^nj!·2^j(frac{1}{j!}sum_{k=0}^j(-1)^k·C_j^k·(j-k)^i) ]

    [sum_{j=0}^n2^jsum_{k=0}^j(-1)^k·C_j^k·sum_{i=0}^n(j-k)^i ]

    [sum_{j=0}^n2^j·j!sum_{k=0}^jfrac{(-1)^k}{k!}frac{sum_{i=0}^n(j-k)^i}{(j-k)!} ]

    [sum_{j=0}^n2^j·j!sum_{k=0}^jfrac{(-1)^k}{k!}frac{(j-k)^{n+1}-1}{(j-k)!(j-k-1)} ]

    不难观察到后半部分是卷积的形式,(NTT)预处理即可。
    时间复杂度(O(nlogn))


    定义(Bell)(B(n)=sum_{i=1}^{n}S(n,i))。表示(i)个不同的球分成任意个非空集合的方案数。
    我们可以得到式子

    [B(n)=sum_{i=0}^{n-1}C_{n-1}^iB(i) ]

    证明:考虑组合意义,枚举与(1)号球在不同集合内的个数,相当于划分为了子问题处理。
    (S^{'}(n,i)=S(n,i)i !),表示集合不相同的第二类斯特林数。同理定义(B^{'}(n))

    [B^{'}(n)=sum_{i=1}^{n}C_n^iB^{'}(n-i) ]

    证明:集合之间已经没有关系了,枚举第一个集合中的元素个数,其他的相当于划分为了子问题处理。
    设题目所求

    [sum_{i=0}^{n}sum_{j=0}^{i}S(i,j) imes 2^j imes (j!)=sum_{i=0}^nf(i) ]

    [f(n)=sum_{i=0}^{n}S(i,j)j !2^j ]

    前面部分相当于(B'(n)),后面部分可以看做每一个集合有两种颜色,每次在一种颜色中放球。
    那么,类似于前面的(S',B')我们可以写出

    [f(n)=2sum_{i=1}^nC_n^if(n-i) ]

    即考虑第一个集合中的元素个数,以及当前集合的颜色。

    将组合数拆开考虑,得到。

    [f(n)=2n!sum_{i=1}^{n}frac{1}{i !}frac{f(n-i)}{(n-i)!} ]

    方法二:(CDQ)分治(+FFT)/分治(FFT)

    将式子稍微改写一下

    [frac{f(n)}{n!}=sum_{i=0}^{n-1}frac{2}{i !}frac{f(i)}{(n-i)!} ]

    (g(i)=frac{2}{i !})。可以使用(CDQ)分治+(FFT)来实现。

    我们观察到如果要算出(f(n)),(f(1..n-1))就必须求得,于是,我们进行分治,每次左侧的(f)值可以对右侧的值产生贡献,贡献就是(f(i))(g(i))的卷积。这就是一个(CDQ)分治,时间复杂度(O(nlog^2n))

    方法三:生成函数+多项式求逆

    [frac{f(n)}{n!}=sum_{i=0}^{n-1}frac{2}{i !}frac{f(i)}{(n-i)!} ]

    (F(x))(f(n))(EGF),(G(n))(g(i)=frac{2}{i !})(OEG),特别注意的是,(g(0)=0)

    所以我们有(F(x)=F(x)G(x)+1)。需要(+1)的原因是(g(0)=0,f(0)=1)。于是我们解一下方程

    [F(x)=frac{1}{1-G(x)} ]

    多项式求逆即可。
    时间复杂度(O(nlogn))


    方法一代码

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define RG register
    #define MOD 998244353
    #define MAX 500000
    const int pr=3;
    const int phi=MOD-1;
    int fpow(int a,int b)
    {
    	int s=1;
    	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    	return s;
    }
    int r[MAX],N,M,l;
    int jc[MAX],inv[MAX];
    int a[MAX],b[MAX],S[MAX];
    void NTT(int *P,int opt)
    {
    	for(int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
    	for(int i=1;i<N;i<<=1)
    	{
    		int W=fpow(pr,phi/(i<<1));
    		for(int p=i<<1,j=0;j<N;j+=p)
    		{
    			int w=1;
    			for(int k=0;k<i;++k,w=1ll*w*W%MOD)
    			{
    				int X=P[j+k],Y=1ll*w*P[i+j+k]%MOD;
    				P[j+k]=(X+Y)%MOD;P[i+j+k]=((X-Y)%MOD+MOD)%MOD;
    			}
    		}
    	}
    	if(opt==-1)reverse(&P[1],&P[N]);
    }
    void Work()
    {
    	M+=N;
    	for(N=1;N<=M;N<<=1)++l;
    	for(int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    	NTT(a,1);NTT(b,1);
    	for(int i=0;i<N;++i)a[i]=1ll*a[i]*b[i]%MOD;
    	NTT(a,-1);
    	for(int i=0,inv=fpow(N,MOD-2);i<N;++i)a[i]=1ll*a[i]*inv%MOD;
    }
    int n,ans;
    int main()
    {
    	scanf("%d",&n);
    	N=M=n;
    	jc[0]=inv[0]=1;
    	for(int i=1;i<=n;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    	for(int i=1;i<=n;++i)inv[i]=fpow(jc[i],MOD-2);
    	for(int i=0;i<=n;++i)a[i]=(i&1)?-1:1;
    	for(int i=0;i<=n;++i)a[i]=(1ll*a[i]*inv[i]%MOD+MOD)%MOD;
    	for(int i=2;i<=n;++i)b[i]=(fpow(i,n+1)-1+MOD)%MOD;
    	for(int i=2;i<=n;++i)b[i]=1ll*b[i]*inv[i]%MOD;
    	for(int i=2;i<=n;++i)b[i]=1ll*b[i]*fpow(i-1,MOD-2)%MOD;
    	b[0]=1;b[1]=n+1;
    	Work();
    	for(int i=0,p=1;i<=n;++i,p=(p+p)%MOD)ans=(ans+1ll*jc[i]*p%MOD*a[i]%MOD)%MOD;
    	printf("%d
    ",ans);
    	return 0;
    }
    
    

    方法二代码

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define RG register
    #define MAX 333333
    #define MOD (998244353)
    inline int read()
    {
        RG int x=0,t=1;RG char ch=getchar();
        while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
        if(ch=='-')t=-1,ch=getchar();
        while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
        return x*t;
    }
    int fpow(int a,int b)
    {
    	int s=1;
    	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    	return s;
    }
    int jc[MAX],inv[MAX],jv[MAX],n;
    int f[MAX],g[MAX];
    void prepare(int N)
    {
    	jc[0]=jv[0]=jc[1]=inv[1]=jv[1]=1;
    	for(int i=2;i<=N;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    	for(int i=2;i<=N;++i)inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
    	for(int i=2;i<=N;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
    	for(int i=1;i<=N;++i)g[i]=(jv[i]+jv[i])%MOD;
    }
    int r[MAX];
    void NTT(int *P,int len,int opt)
    {
    	int N,l=0;for(N=1;N<len;N<<=1)++l;
    	for(int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    	for(int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
    	for(int i=1;i<N;i<<=1)
    	{
    		int W=fpow(3,(MOD-1)/(i<<1));
    		for(int p=i<<1,j=0;j<N;j+=p)
    		{
    			int w=1;
    			for(int k=0;k<i;++k,w=1ll*w*W%MOD)
    			{
    				int X=P[j+k],Y=1ll*w*P[i+j+k]%MOD;
    				P[j+k]=(X+Y)%MOD;P[i+j+k]=(X+MOD-Y)%MOD;
    			}
    		}
    	}
    	if(opt==-1)
    	{
    		reverse(&P[1],&P[N]);
    		for(int i=0,inv=fpow(N,MOD-2);i<N;++i)P[i]=1ll*P[i]*inv%MOD;
    	}
    }
    int A[MAX],B[MAX];
    void CDQ(int l,int r)
    {
    	if(l==r)return;
    	int mid=(l+r)>>1;
    	CDQ(l,mid);
    	for(int i=0;i<=mid-l;++i)A[i]=f[i+l];
    	for(int i=0;i<=r-l;++i)B[i]=g[i];
    	int N;for(N=1;N<=mid+r-l-l+1;N<<=1);
    	NTT(A,N,1);NTT(B,N,1);
    	for(int i=0;i<N;++i)A[i]=1ll*A[i]*B[i]%MOD;
    	NTT(A,N,-1);
    	for(int i=mid-l+1;i<=r-l;++i)f[i+l]=(A[i]+f[i+l])%MOD;
    	for(int i=0;i<N;++i)A[i]=B[i]=0;
    	CDQ(mid+1,r);
    }
    int main()
    {
    	prepare(n=read());
    	f[0]=1;
    	CDQ(0,n);
    	int ans=0;
    	for(int i=0;i<=n;++i)ans=(ans+1ll*f[i]*jc[i]%MOD)%MOD;
    	printf("%d
    ",ans);
    	return 0;
    }
    
    

    方法三代码

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define RG register
    #define MAX 333333
    #define MOD (998244353)
    inline int read()
    {
        RG int x=0,t=1;RG char ch=getchar();
        while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
        if(ch=='-')t=-1,ch=getchar();
        while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
        return x*t;
    }
    int fpow(int a,int b)
    {
    	int s=1;
    	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    	return s;
    }
    int inv[MAX],jc[MAX],n;
    int f[MAX],g[MAX];
    void prepare(int N)
    {
    	inv[1]=inv[0]=jc[0]=jc[1]=1;
    	for(RG int i=2;i<=N;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    	for(RG int i=2;i<=N;++i)inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
    	for(RG int i=2;i<=N;++i)inv[i]=1ll*inv[i]*inv[i-1]%MOD;
    	for(RG int i=1;i<=N;++i)g[i]=(inv[i]+inv[i])%MOD;
    }
    int r[MAX],W[MAX];
    void NTT(int *P,int len,int opt)
    {
    	int N,l=0;for(N=1;N<len;N<<=1)++l;
    	for(RG int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    	for(RG int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
    	for(RG int i=1;i<N;i<<=1)
    	{
    		RG int w=fpow(3,(MOD-1)/(i<<1));
    		W[0]=1;for(RG int k=1;k<i;++k)W[k]=1ll*W[k-1]*w%MOD;
    		for(RG int p=i<<1,j=0;j<N;j+=p)
    			for(RG int k=0;k<i;++k)
    			{
    				int X=P[j+k],Y=1ll*W[k]*P[i+j+k]%MOD;
    				P[j+k]=(X+Y)%MOD;P[i+j+k]=(X+MOD-Y)%MOD;
    			}
    	}
    	if(opt==-1)
    	{
    		reverse(&P[1],&P[N]);
    		for(RG int i=0,inv=fpow(N,MOD-2);i<N;++i)P[i]=1ll*P[i]*inv%MOD;
    	}
    }
    int A[MAX],B[MAX];
    void Inv(int *a,int *b,int len)
    {
    	if(len==1){b[0]=fpow(a[0],MOD-2);return;}
    	Inv(a,b,len>>1);
    	for(RG int i=0;i<len;++i)A[i]=a[i],B[i]=b[i];
    	NTT(A,len<<1,1);NTT(B,len<<1,1);
    	for(RG int i=0;i<(len<<1);++i)B[i]=1ll*A[i]*B[i]%MOD*B[i]%MOD;
    	NTT(B,len<<1,-1);
    	for(RG int i=0;i<len;++i)b[i]=((b[i]+b[i])%MOD+MOD-B[i])%MOD;
    	for(RG int i=0;i<(len<<1);++i)A[i]=B[i]=0;
    }
    int main()
    {
    	prepare(n=read());
    	int N;for(N=1;N<=n;N<<=1);
    	for(RG int i=0;i<N;++i)g[i]=(MOD-g[i])%MOD;g[0]=1;
    	Inv(g,f,N);
    	int ans=0;
    	for(RG int i=0;i<=n;++i)ans=(ans+1ll*f[i]*jc[i]%MOD)%MOD;
    	printf("%d
    ",ans);
    	return 0;
    }
    
    
  • 相关阅读:
    jquery的一些用法
    js函数:setInterval()/clearInterval()——js网页计时器
    oracle递归查询
    子查询
    oracle分析函数
    前端的UI设计与交互之设计原则篇
    用js编解码base64
    总结的Javascript插件
    vuex2中使用mapMutations/mapActions报错解决方法 BabelLoaderError: SyntaxError: Unexpected token
    form表单里的故事
  • 原文地址:https://www.cnblogs.com/cjyyb/p/8798359.html
Copyright © 2011-2022 走看看