• 【CTS2019】珍珠(生成函数)


    【CTS2019】珍珠(生成函数)

    题面

    LOJ
    洛谷

    题解

    lun题可海星。
    首先一个大暴力(sb)(dp)是设(f[i][S])表示当前考虑完了前(i)个珍珠,(S)集合中这些颜色的珍珠当前还有一个没有匹配。这个随便转移就行了。
    然后发现并没有任何需要记录下确切的哪些颜色是奇数个,只需要记录有多少种就行了。
    这样子可以做到(O(nd))
    从这里我们看出,最终能够匹配出来的对数恰好等于((n-|S|)/2),总个数减去奇数颜色的个数的一半。
    首先如果我们能够知道每种颜色的奇偶情况,那么每个颜色都可以写成一个指数型生成函数,然后全部乘起来的第(n)项就是答案。
    其中,奇数的生成函数是:(A(x)=frac{e^x-e^{-x}}{2}),偶数的生成函数是:(B(x)=frac{e^x+e^{-x}}{2})
    那么我们假设枚举出来有(p)个颜色是奇数,方案数就是([x^n]A^p(x)B^{d-p}(x))
    所以答案式:

    [egin{aligned} ans&=frac{n!}{2^d}sum_{p=0}^{n-2m}[x^n]{dchoose p}(e^x-e^{-x})^p(e^x+e^{-x})^P\ &=frac{n!}{2^d}sum_{p=0}^{n-2m}[x^n]{dchoose p}sum_{i=0}^psum_{j=0}^{d-p}(-1)^{p-i}{pchoose i}{d-pchoose j}e^{(2i+2j-d)x}\ &=frac{1}{2^d}sum_{p=0}^{n-2m}{dchoose p}sum_{i=0}^psum_{j=0}^{d-p}(-1)^{p-i}{pchoose i}{d-pchoose j}(2i+2j-d)^n\ &=frac{1}{2^d}d!sum_{p=0}^{n-2m}sum_{i=0}^psum_{j=0}^{d-p}frac{(-1)^{p-i}(2i+2j-d)^n}{i!(p-i)!j!(d-p-j)!} end{aligned}]

    这样子直接暴力可以做到(O(d^3))
    考虑接着往下进行一些变化,首先我们发现我们的这个式子麻烦在(i,j)既有分开的项,又有(i+j)的项,那么把这两个部分给分开看。

    [egin{aligned} ans&=frac{1}{2^d}d!sum_{p=0}^{n-2m}sum_{i=0}^psum_{j=0}^{d-p}frac{(-1)^{p-i}(2i+2j-d)^n}{i!(p-i)!j!(d-p-j)!}\ &=frac{1}{2^d}d!sum_{p=0}^{n-2m}sum_{i=0}^psum_{j=0}^{d-p}(-1)^{p-i}(2(i+j)-d)^nfrac{1}{i!j!}frac{1}{(p-i)!(d-p-j)!}\ &=frac{1}{2^d}d!sum_{p=0}^{n-2m}sum_{i=0}^psum_{j=0}^{d-p}(-1)^{p-i}frac{(2(i+j)-d)^n}{(i+j)!(d-(i+j))!}frac{(i+j)!}{i!j!}frac{(d-(i+j))!}{(p-i)!(d-p-j)!}\ &=frac{1}{2^d}d!sum_{p=0}^{n-2m}sum_{i=0}^psum_{j=0}^{d-p}(-1)^{p-i}frac{(2(i+j)-d)^n}{(i+j)!(d-(i+j))!}{i+jchoose i}{d-(i+j)choose p-i}\ &=frac{1}{2^d}sum_{p=0}^{n-2m}sum_{i=0}^psum_{j=0}^{d-p}(-1)^{p-i}(2(i+j)-d)^n{dchoose i+j}{i+jchoose i}{d-(i+j)choose p-i}\ end{aligned}]

    这样子再往下我似乎就不会推了,难受。
    (memset0)的做法可以在洛谷的题解内看到,我这里大致的复述一下。
    考虑把后面的组合数转变为格路问题,(i+jchoose i)是从((0,0))走到((i,j))的方案数,(d-(i+j)choose p-i)是从((i,j))走到((p,d-p))的方案数。
    然后这里还有一个((-1)^{p-i})的贡献,格路这个东西也可以写成一个生成函数,既然作为杨辉三角的一层,所以生成函数就是((1+x)^d)的形式。
    然后这里枚举的时候并没有单独出现过(j)了,所以只需要枚举(i+j)会更加方便。

    [egin{aligned} ans&=frac{1}{2^d}sum_{i=0}^d(2i-d)^n{dchoose i}sum_{p=0}^{n-2m}[x^p](1+x)^i(1-x)^{d-i} end{aligned}]

    讲讲这个为什么是对的,首先((-1)^{p-i})表示只和第二次走的距离相关,而横坐标总共要走(p)步,第一次走不会产生符号,所以就是((1+x)^i),第二次会产生符号,之和走的横坐标的步数相关,奇数步是(-1),偶数步是(1),所以是((1-x)^{d-i})
    那么令(F(i,d)=sum_{p=0}^{n-2m}[x^p](1+x)^i(1-x)^{d-i})
    所以有

    [egin{aligned} ans&=frac{1}{2^d}sum_{i=0}^d(2i-d)^n{dchoose i}sum_{p=0}^{n-2m}[x^p](1+x)^i(1-x)^{d-i}\ &=frac{1}{2^d}sum_{i=0}^d(2i-d)^n{dchoose i}F(i,d) end{aligned}]

    考虑怎么计算(F),那么考虑能不能递推。
    首先边界(F(0,0)=1)

    [egin{aligned} F(0,d)&=sum_{k=0}^{n-2m}[x^k](1-x)^d\ &=sum_{k=0}^{n-2m}{dchoose k}(-1)^k\ &=sum_{k=0}^{n-2m}(-1)^k({d-1choose k}+{d-1choose k-1})\ &=sum_{k=0}^{n-2m}(-1)^k{d-1choose k}+sum_{k=0}^{n-2m-1}(-1)^{k+1}{d-1choose k}\ &=(-1)^{n-2m}{d-1choose n-2m} end{aligned}]

    然后考虑其他的情况,

    [egin{aligned} F(i,d)&=sum_{k=0}^{n-2m}[x^k](1-x)^{d-i}(1+x)^i\ &=sum_{k=0}^{n-2m}[x^k](1+x)(1+x)^{i-1}(1-x)^{d-i}\ &=sum_{k=0}^{n-2m}[x^k](-(1-x)+2)(1+x)^{i-1}(1-x)^{d-i}\ &=-sum_{k=0}^{n-2m}[x^k](1+x)^{i-1}(1-x)^{d-i+1}+2sum_{k=0}^{n-2m}[x^k](1+x)^{i-1}(1-x)^{d-i}\ &=-F(i-1,d)+2F(i-1,d-1) end{aligned}]

    于是这个东西肉眼可见的可以继续在网格图上走,即有两种走法,一种是沿着横坐标走一步,另外一种是两个坐标一起走一步。
    发现无论如何横坐标都要走,所以唯一需要决策的只有竖坐标的移动,而两种移动方式分别产生(-1)(2)的贡献,那么我们枚举起点就可以知道竖坐标要走多少步,然后组合数就可以随便算了。
    所以得到:

    [F(i,d)=sum_{j=0}^d F(0,j){ichoose d-j}(-1)^{i+j-d}2^{d-j} ]

    这样子愉快的把答案式给展开:

    [egin{aligned} ans&=frac{1}{2^d}sum_{i=0}^d(2i-d)^n{dchoose i}F(i,d)\ &=frac{1}{2^d}sum_{i=0}^d(2i-d)^n{dchoose i}sum_{j=0}^d F(0,j){ichoose d-j}(-1)^{i+j-d}2^{d-j}\ &=frac{d!}{2^d}sum_{i=0}^dsum_{j=0}^d frac{(-1)^{d-i-j}}{(i+j-d)!}frac{(2i-d)^n}{(d-i)!}frac{F(0,j)2^{d-j}}{(d-j)!} end{aligned}]

    后面两个东西可以处理成一个卷积,而前面那一项恰好只和(i+j)相关,那么直接(NTT)乘起来然后枚举一下就能算了。


    然而这样子的推导很麻烦,lun课件中的方法是一致的,
    但是中间推导的部分更加简单。
    不一样的地方在于

    [ans=frac{n!}{2^d}sum_{p=0}^{n-2m}[x^n]{dchoose p}(e^x-e^{-x})^p(e^x+e^{-x})^P ]

    原本这里把奇偶分开然后统一的计算([x^n]),那么现在写成这样子:

    [[x^n][y^p](y(e^x-e^{-x})+(e^x+e^{-x}))^d ]

    这样自己就一个式子钦定了奇数被选的次数。
    也就是:

    [egin{aligned} ans&=frac{n!}{2^d}sum_{p=0}^{n-2m}[x^n][y^p](y(e^x-e^{-x})+(e^x+e^{-x}))^d\ &=frac{n!}{2^d}sum_{p=0}^{n-2m}[x^n][y^p](e^x(1+y)+e^{-x}(1-y))^d\ &=frac{n!}{2^d}sum_{p=0}^{n-2m}sum_{i=0}^d e^{(2i-d)x}(1+y)^i(1-y)^{d-i}[x^n][y^p]\ &=frac{n!}{2^d}sum_{p=0}^{n-2m}(2i-d)^n sum_{i=0}^d((1+y)^i(1-y)^{d-i})[y^p] end{aligned}]

    然后令(F(i,d))为后面那个(sum),就和前面的推导一致了。
    但是这样子方便多了,不需要再转化成格路来进行推导。


    然后还有(1t5t)的方法,
    我们只考虑枚举奇数至少有多少个,这样子我们枚举完奇数个数之后剩下的随意,也就是(e^{(d-p)x})
    那么设(f_i)表示强制有(i)个为奇数的方案数,
    有:

    [egin{aligned} f_i&=[x^n]{dchoose i}(frac{e^x-e^{-x}}{2})^ie^{(d-i)x}\ &=[x^n]frac{1}{2^i}frac{d!}{i!(d-i)!}e^{(d-i)x}sum_{j=0}^i {ichoose j}(-1)^j e^{(i-2j)x}\ &=[x^n]frac{1}{2^i}frac{d!}{i!(d-i)!}sum_{j=0}^i {ichoose j}(-1)^j e^{(d-2j)x}\ &=frac{1}{2^i}frac{d!}{i!(d-i)!}sum_{j=0}^i frac{i!}{j!(i-j)!}(-1)^j (d-2j)^n end{aligned}]

    这样子就可以卷积算出所有的(f),然后用二项式反演去求解所有的恰好然后计算答案了。


    代码是前两种做法的。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define MOD 998244353
    #define MAX 550550
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    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 W[MAX],P[MAX],r[MAX];
    void NTT(int *P,int len,int opt)
    {
    	int l=0,N;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));W[0]=1;
    		for(int k=1;k<i;++k)W[k]=1ll*W[k-1]*w%MOD;
    		for(int j=0,p=i<<1;j<N;j+=p)
    			for(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(int i=0,inv=fpow(N,MOD-2);i<N;++i)P[i]=1ll*P[i]*inv%MOD;
    	}
    }
    int jc[MAX],jv[MAX],inv[MAX];
    int C(int n,int m){if(n<0||m<0||n<m)return 0;return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
    int A[MAX],B[MAX],F[MAX];
    int D,n,m,ans;
    int main()
    {
    	D=read();n=read();m=read();
    	jc[0]=jv[0]=inv[0]=inv[1]=1;
    	for(int i=2;i<=D;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=1;i<=D;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    	for(int i=1;i<=D;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
    	for(int i=0;i<=D;++i)A[i]=1ll*fpow(((i+i-D)%MOD+MOD)%MOD,n)*jv[D-i]%MOD;
    	for(int i=1;i<=D;++i)F[i]=1ll*((n&1)?MOD-1:1)%MOD*C(i-1,n-2*m)%MOD;F[0]=1;
    	for(int i=0;i<=D;++i)B[i]=1ll*fpow(2,D-i)*jv[D-i]%MOD*F[i]%MOD;
    	int N;for(N=1;N<=D+D;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=D;i<=D+D;++i)ans=(ans+1ll*(((D+i)&1)?MOD-1:1)*jv[i-D]%MOD*A[i])%MOD;
    	ans=1ll*ans*jc[D]%MOD*fpow(fpow(2,D),MOD-2)%MOD;
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    软件行业从事人员,计算机基础须知(三)---DOS命令相关
    软件行业从事人员,计算机基础须知(二)---系统介绍和进制转换说明
    软件行业从事人员,计算机基础须知(一)
    python操作数据库步骤以及操作过程中经常出现的异常
    selenium中验证码识别简单封装
    selenium对验证码识别校验解决方法
    retina屏幕截取验证码
    Nginx的负载均衡的那点事
    子网划分
    iptables 规则整理
  • 原文地址:https://www.cnblogs.com/cjyyb/p/10912960.html
Copyright © 2020-2023  润新知