题目链接

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\ (k\in \mathbb{Z})\) 的路径。
这意味着我们考虑路径长度时只需要考虑它的奇偶性,如果奇偶性相同,那么当路径长度足够大时一定能满足长度相同。
性质 4:状态 \(u\) 和状态 \(v\) 连通当且仅当存在 \(l\in\{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=n\cdot 2^{n\choose 2} \]

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

\[g_n=n\cdot (2^{n\choose 2}-2^{n-1\choose 2}) \]

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

\[e_n=2^{n\choose 2} \]

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

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

\[C=\ln E \]

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

\[\frac{D}{e^x}=\sum_{i\ge 0}i\cdot \frac{(C-x)^i}{i!}=\sum_{i\ge 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}{n\choose i}2^{i(n-i)} \]

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

\[H=\sum_{i\ge 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(n\log 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\n",ans);
	return 0;
}