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;
    }
    
  • 相关阅读:
    图标工具箱
    第40课 程序的内存布局
    第39课 程序中的三国天下
    第38课 动态内存分配
    第37课 指针阅读技巧分析
    第36课 函数与指针分析
    第35课 数组参数和指针参数分析
    第34课 多维数组和多维指针
    第33课 main函数与命令行参数
    第32课 数组指针和指针数组分析
  • 原文地址:https://www.cnblogs.com/suncongbo/p/14479176.html
Copyright © 2011-2022 走看看