zoukankan      html  css  js  c++  java
  • UOJ #498 新年的追逐战 (多项式、组合计数)

    题目链接

    https://uoj.ac/problem/498

    题解

    做了 8 个小时,最后搞出来一个极其麻烦的做法。。字母用光警告

    首先考虑一下图论背景的转化:
    这个模型相当于 (n) 个图上同时游走,每次在每个图上走一步,如果从一个状态可达另一个状态则它们连通。
    性质 1:如果状态 (u=(u_1,u_2,...,u_n))(v=(v_1,v_2,...,v_n)) 满足存在 (k) 使得在图 (k)(u_k)(v_k) 不连通,则状态 (u) 和状态 (v) 不连通。特别地,对于一个孤立点而言,它不和自己连通。
    那么我们就可以从每个图各取出一个连通块考虑,再把所有的方案的答案加起来。特别地,除特殊说明外,孤立点不算作连通块/连通图。
    性质 2:状态 (u) 和状态 (v) 连通当且仅当存在 (l) 使 (forall k),在图 (k) 中存在从 (u_k)(v_k) 的长度为 (l) 的路径。
    性质 3:对于一张无向连通图而言,若图中存在 (u)(v) 长度为 (l) 的路径,则存在 (u)(v) 长度为 (l+2k (kin mathbb{Z})) 的路径。
    这意味着我们考虑路径长度时只需要考虑它的奇偶性,如果奇偶性相同,那么当路径长度足够大时一定能满足长度相同。
    性质 4:状态 (u) 和状态 (v) 连通当且仅当存在 (lin{0,1}) 使 (forall k),在图 (k) 中存在从 (u_k)(v_k) 的长度 (mod 2=l) 的路径。
    性质 5:对于一张无向连通图而言,对于任意的 (u,v),若该图是二分图,则 (u)(v) 之间只存在一种奇偶性的长度;否则,长度为奇数和偶数的路径都存在。
    性质 6:回到原题,假设 (n) 个图都是连通的,设 (n) 个图中有 (b) 个图是二分图,对答案的贡献是 (2^{max(0,b-1)}).
    那么 (n) 个图(不一定都要连通),对答案的贡献就是

    [prod(第 i 个图的总点数)-prod(第 i 个图的孤立点数)\ +frac{1}{2}(prod(第 i 个图中连通块数+第 i 个图中二分图连通块数)+prod(第 i 个图中连通块数-第 i 个图中二分图连通块数))]

    后面一部分的含义是,先不考虑和 (0)(max) 的问题,相当于每取一个二分图连通块权值要乘以 (2),最后再除以 (2). 这样会导致没有二分图的情况只被算了半次,那么就把它加上。
    因此我们可以分这四部分计算答案,分别计算总点数、孤立点数、连通块数、二分图连通块数的乘积之和。每部分是 (n) 个图的 (prod) 的形式,所以可以对每个图求出它所有形态下的和,然后乘起来。

    接下来就是生成函数一顿爆推,用小写字母表示一个序列,对应的大写字母表示其对应的指数生成函数。
    (生成函数的式子和 DP 相比异常简洁,然而最开始我全都是拿 DP 推的……)
    (1) 总点数
    (F) 表示 (n) 个点的图的总点数。则有

    [f_n=ncdot 2^{nchoose 2} ]

    (2) 孤立点数
    (G) 表示 (n) 个点的图的总孤立点数。则有

    [g_n=ncdot (2^{nchoose 2}-2^{n-1choose 2}) ]

    (3) 连通块数
    (E) 表示 (n) 个点的图的个数。则有

    [e_n=2^{nchoose 2} ]

    (C) 表示 (n) 个点连通图(含孤立点)的个数。
    一个图是由若干个连通块(含孤立点)彼此之间无序构成的,因此

    [E=sum_{ige 0}frac{C^i}{i!}=e^{C} ]

    [C=ln E ]

    (D) 表示 (n) 个点的所有图中连通块数的总和。
    先求出没有孤立点的时候的答案,再把孤立点的贡献 (e^x) 乘上

    [frac{D}{e^x}=sum_{ige 0}icdot frac{(C-x)^i}{i!}=sum_{ige 1}frac{(C-x)^i}{(i-1)!}=(C-x)e^{C-x} ]

    [D=(C-x)e^C=(C-x)E ]

    (4) 二分图连通块数
    这部分是重点。
    先考虑如何计算 (n) 个点二分图的个数。
    考虑黑白染色,一个连通(含孤立点)的二分图一定只有 (2) 种黑白染色方案,一个二分图的黑白染色方案数量是 (2) 的连通块个数次幂。
    (H) 表示对 (n) 个点的图进行黑白染色,((染色方案,该方案对应的二分图)) 这个二元组的个数。

    [h_n=sum^n_{i=0}{nchoose i}2^{i(n-i)} ]

    这个的计算可以把 (i(n-i)) 拆成 ({nchoose 2}-{ichoose 2}-{n-ichoose 2}),然后 NTT.
    (R) 表示 (n) 个点的连通二分图(含孤立点)的个数。这其实可以视为 ((染色方案,该方案对应的连通二分图)) 二元组的个数乘以 (frac{1}{2}).
    (B) 表示 (n) 个点的二分图的个数。
    神奇的事情来了:一个二分图也是由若干连通块(含孤立点)彼此之间无序构成的。而且每个连通块有 (2) 种染色方案相当于每加一个连通块要乘以 (2) 的权值,所以

    [H=sum_{ige 0}frac{R^i2^i}{i!}=e^{2R} ]

    [R=frac{1}{2}ln H ]

    [B=e^R ]

    (所以如果只是要求二分图的个数的话,直接 (B=sqrt H) 就可以了。)
    (S) 表示 (n) 个点的图总共有多少个二分图连通块。先求出 ((R-x)e^{R-x}) 表示 (n) 个点、每个连通块都是二分图(不含孤立点)的图总连通块个数,再乘上其余的非二分图连通块(含孤立点)。

    [S=(R-x)e^{R-x}cdot e^{C-(R-x)}=(R-x)e^C=(R-x)E ]

    最后答案就等于 (prod f_{m_i}-prod g_{m_i}+prod (d_{m_i}+s_{m_i})+prod (d_{m_i}-s_{m_i}))

    于是终于做完了,时间复杂度 (O(nlog n)). 实际上这里并没有求 exp 的必要,所以常数并没有看上去那么大,至少 600ms 之内能跑出来。

    尽管我尽力避免用重字母,最后还是重了一个 (e)/kk

    代码

    #include<bits/stdc++.h>
    #define llong long long
    #define mkpr make_pair
    #define x first
    #define y second
    #define iter iterator
    #define riter reverse_iterator
    #define y1 Lorem_ipsum_
    #define tm dolor_sit_amet_
    using namespace std;
    
    inline int read()
    {
    	int x = 0,f = 1; char ch = getchar();
    	for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
    	for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
    	return x*f;
    }
    
    const int mxN = 1<<19;
    const int lgN = 19;
    const int P = 998244353;
    
    llong fact[mxN+3],facti[mxN+3],inv[mxN+3],pwc2[mxN+3];
    
    llong quickpow(llong x,llong y)
    {
    	llong cur = x,ret = 1ll;
    	for(int i=0; y; i++)
    	{
    		if(y&(1ll<<i)) {y-=(1ll<<i); ret = ret*cur%P;}
    		cur = cur*cur%P;
    	}
    	return ret;
    }
    llong mulinv(llong x) {return quickpow(x,P-2);}
    llong comb(llong x,llong y) {return x<0ll||y<0ll||x<y?0ll:fact[x]*facti[y]%P*facti[x-y]%P;}
    
    void initfact()
    {
    	fact[0] = 1ll; for(int i=1; i<=mxN; i++) fact[i] = fact[i-1]*i%P;
    	facti[mxN] = quickpow(fact[mxN],P-2); for(int i=mxN-1; i>=0; i--) facti[i] = facti[i+1]*(i+1ll)%P;
    	for(int i=1; i<=mxN; i++) inv[i] = facti[i]*fact[i-1]%P;
    	for(int i=0; i<=mxN; i++) pwc2[i] = quickpow(2ll,1ll*i*(i-1ll)/2ll);
    }
    
    namespace FFT
    {
    	const int G = 3;
    	llong aux1[mxN+3],aux2[mxN+3],aux3[mxN+3],aux4[mxN+3],aux5[mxN+3],aux6[mxN+3],aux7[mxN+3];
    	int fftid[mxN+3]; llong sexp[mxN+3];
    	int getdgr(int n) {int ret = 1; while(ret<=n) ret<<=1; return ret;}
    	void init_fftid(int dgr)
    	{
    		for(int i=1; i<dgr; i++) fftid[i] = (fftid[i>>1]>>1)|((i&1)*(dgr>>1));
    	}
    	void ntt(int dgr,int coe,llong poly[],llong ret[])
    	{
    		init_fftid(dgr);
    		if(poly==ret) {for(int i=0; i<dgr; i++) if(i<fftid[i]) swap(ret[i],ret[fftid[i]]);}
    		else {for(int i=0; i<dgr; i++) ret[i] = poly[fftid[i]];}
    		for(int i=1; i<dgr; i<<=1)
    		{
    			llong tmp = quickpow(G,(P-1)/(i<<1)); if(coe==-1) {tmp = mulinv(tmp);}
    			sexp[0] = 1ll; for(int j=1; j<i; j++) sexp[j] = sexp[j-1]*tmp%P;
    			for(int j=0; j<dgr; j+=(i<<1))
    			{
    				for(llong *k=ret+j,*kk=sexp; k<ret+i+j; k++,kk++)
    				{
    					llong x = *k,y = (*kk)*k[i]%P;
    					*k = x+y>=P?x+y-P:x+y;
    					k[i] = x-y<0ll?x-y+P:x-y;
    				}
    			}
    		}
    		if(coe==-1)
    		{
    			llong tmp = mulinv(dgr); for(int i=0; i<dgr; i++) ret[i] = ret[i]*tmp%P;
    		}
    	}
    	void polymul(int dgr,llong poly1[],llong poly2[],llong ret[])
    	{
    		memset(poly1+dgr,0,sizeof(llong)*dgr); memset(poly2+dgr,0,sizeof(llong)*dgr);
    		ntt(dgr<<1,1,poly1,aux1); ntt(dgr<<1,1,poly2,aux2);
    		for(int i=0; i<(dgr<<1); i++) ret[i] = aux1[i]*aux2[i]%P;
    		ntt(dgr<<1,-1,ret,ret);
    	}
    	void polyinv(int dgr,llong poly[],llong ret[])
    	{
    		memset(ret,0,sizeof(llong)*(dgr<<1)); ret[0] = mulinv(poly[0]);
    		for(int i=1; i<dgr; i<<=1)
    		{
    			ntt(i<<2,1,ret,aux3);
    			for(int j=0; j<(i<<2); j++) aux4[j] = j<(i<<1)?poly[j]:0ll;
    			ntt(i<<2,1,aux4,aux4);
    			for(int j=0; j<(i<<2); j++) aux4[j] = (2ll*aux3[j]-aux3[j]*aux3[j]%P*aux4[j]%P+P)%P;
    			ntt(i<<2,-1,aux4,aux4);
    			for(int j=0; j<(i<<1); j++) ret[j] = aux4[j];
    		}
    	}
    	void polyln(int dgr,llong poly[],llong ret[])
    	{
    		for(int i=dgr-2; i>=0; i--) aux5[i] = poly[i+1]*(i+1ll)%P; aux5[dgr-1] = 0ll;
    		polyinv(dgr,poly,aux6);
    		polymul(dgr,aux5,aux6,aux7);
    		ret[0] = 0ll; for(int i=1; i<dgr; i++) ret[i] = aux7[i-1]*inv[i]%P;
    	}
    }
    using FFT::ntt;
    using FFT::getdgr;
    using FFT::polymul;
    using FFT::polyinv;
    using FFT::polyln;
    
    int dgr,q;
    int n[mxN+3];
    llong f[mxN+3],g[mxN+3],e[mxN+3],c[mxN+3],d[mxN+3],h[mxN+3],r[mxN+3],s[mxN+3],w[mxN+3];
    llong aux1[mxN+3],aux2[mxN+3],aux3[mxN+3],aux4[mxN+3];
    
    int main()
    {
    	initfact();
    	q = read(); for(int i=1; i<=q; i++) {n[i] = read(); dgr = max(dgr,n[i]);}
    	dgr = getdgr(dgr);
    	for(int i=0; i<dgr; i++) {f[i] = pwc2[i]*i%P,g[i] = (pwc2[i]-pwc2[i-1]+P)*i%P;}
    	for(int i=0; i<dgr; i++) {e[i] = pwc2[i]*facti[i]%P;}
    	polyln(dgr,e,c); c[1] = 0ll;
    	polymul(dgr,c,e,d); memset(d+dgr,0,sizeof(llong)*dgr);
    	for(int i=0; i<dgr; i++) aux1[i] = facti[i]; memset(d+dgr,0,sizeof(llong)*dgr);
    	for(int i=0; i<dgr; i++) {aux3[i] = mulinv(pwc2[i]*fact[i]%P);}
    	ntt(dgr<<1,1,aux3,aux3); for(int i=0; i<(dgr<<1); i++) aux3[i] = aux3[i]*aux3[i]%P; ntt(dgr<<1,-1,aux3,aux3);
    	for(int i=0; i<dgr; i++) {h[i] = aux3[i]*pwc2[i]%P;}
    	polyln(dgr,h,r); r[1] = 0ll; for(int i=0; i<dgr; i++) r[i] = r[i]*inv[2]%P;
    	polymul(dgr,r,e,s); memset(s+dgr,0,sizeof(llong)*dgr);
    	llong ans1 = 1ll,ans2 = 1ll,ans3 = 1ll,ans4 = 1ll;
    	for(int i=1; i<=q; i++)
    	{
    		ans1 = ans1*f[n[i]]%P,ans2 = ans2*g[n[i]]%P,
                    ans3 = ans3*(d[n[i]]+s[n[i]])%P*fact[n[i]]%P,ans4 = ans4*(d[n[i]]-s[n[i]]+P)%P*fact[n[i]]%P;
    	}
    	llong ans = (ans1-ans2+(ans3+ans4)*inv[2]+P)%P;
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    Python 学习日记 第七天
    Python 学习日记 第六天
    Python 学习日记 第五天
    Python 学习日记 第四天
    Redis 中的数据类型及基本操作
    Asp.net mvc 中View 的呈现(二)
    Asp.net mvc 中View的呈现(一)
    Asp.net mvc 中Action 方法的执行(三)
    Asp.net mvc 中Action 方法的执行(二)
    Asp.net mvc 中Action 方法的执行(一)
  • 原文地址:https://www.cnblogs.com/suncongbo/p/14479176.html
Copyright © 2011-2022 走看看