题目
点这里看题目。
分析
首先,我们不妨考察 \(n=1\) 的情况。如果认为 \(F(x)\) 为连通无向图的 EGF,则事实上,我们可以直接考虑任意一个连通块和剩下的方案数,连通块个数的 EGF \(C(x)\) 为:
考察 \(n>1\) 的情况,不妨从 \(n=2\) 的简单情况入手。首先,我们给 \(G_1\times G_2\) 赋一个组合含义:我们可以认为是同时在两个图上面进行游走,且每次两个图上都必须经过一条边。那么,我们考察连通块,也就是考察可以经过若干步游走到达的等价类。
首先考察一种平凡的移动方式。在图 \(G_1\times G_2\) 上面,我们如果要从 \((u_1,v_1)\) 出发,则:
-
如果 \(u_1\) 或者 \(v_1\) 没有邻接点,那么这个状态动不了了。
-
否则,我们假设目标状态为 \((u_2,v_2)\)。之后,我们可以先让 \(u_1\) 移动到 \(u_2\),在这个过程中 \(v_1\) 在一条邻接边上反复横跳;之后移动 \(v_1\) 到 \(v_2\),这个过程中 \(u_1\) 也需要反复横跳。
第一种情况是平凡的。第二种情况下,我们首先得保证 \(u_1,u_2\) 和 \(v_1,v_2\) 得各自同一连通块中;其次,\(u_1\rightarrow u_2\) 和 \(v_1\rightarrow v_2\) 的路径奇偶性必须相同。这导向了二分性的考察:如果某一个连通块不是二分图,则我们可以找到奇环,相应地这一条路径的奇偶性可以随便改变,否则奇偶性就不能随便改变(这容易证明)。因此,在第二种情况下,如果我们考察两个连通块的乘积中连通块的个数,则这仅仅和两者是否都是二分图有关。
补充:虽然这仅仅是一种平凡的移动方式,不过在二分图上,可以发现这种移动方式能够到达的状态就是所有能够到达的状态(任何环都是偶环)。而在非二分图上——我们已经讨论过了。
进一步推广到 \(n\) 个图的乘积 \(G=\prod_kG_k\)。我们可以从每个因子 \(G_k\) 取出一个连通块 \(C_k\),再考虑:
-
如果存在大小为 1 的连通块,则此时对 \(G\) 的贡献为 \(\prod_k|V(C_k)|\)。
-
否则,如果连通块中有 \(b\) 个二分图,则对 \(G\) 的贡献为 \(2^{\max\{b-1,0\}}\)。
幂次为 \(b-1\) 的理由是,等价类中的状态只需要保证“相对奇偶性”相同即可,因此需要去掉一个 2 的因子。
之后的过程实际上是纯组合的问题。由于所有的 \(m\) 均已给出,我们可以规避不必要的生成函数。事实上我们只需要分别考虑:
-
存在大小为 1 的连通块,此时 \(G_k\) 如果选出了大小为 1 的连通块,则贡献为 \(a_k=m_k2^{\binom{m_k-1}{2}}\),否则随便选,贡献为 \(b_k=[x^{m_k}]xC’(x)\)。注意这里需要计算连通块的大小。
-
所有连通块大小均大于 1。此时每个二分图贡献 2,而每个非二分图贡献 1。如果设连通二分图的 EGF 为 \(B(x)\),则我们只需要知道 \(c_k=[x^{m_k}]B\exp F\) 和 \(d_k=[x^{m_k}](F-B)\exp F\)。
最终的答案不难得出,即为:
最后考虑一下 \(B(x)\) 怎么求。如果不考虑计算重复的问题,我们可以直接有序地枚举二分图的一部,得到:
而我们注意到,对于一个连通二分图,枚举二分图的一部实际上就是考虑所有的染色方案,而实际上只会有两种不同的染色方案(本质上就是对换黑白)。更进一步地,如果有 \(k\) 个二分连通块,染色方案就是 \(2^k\) 种。因此,我们可以得到:
算一个 \(\ln\) 即可得到 \(B(x)\)。
补充:据说这样的图计数问题和某种“图论生成函数”有关。
定义序列 \(\{a_n\}_{n\ge 0}\) 的 GGF 为:
\[\sum_{n\ge 0}\frac{a_n}{n!2^{\binom{n}{2}}}x^n \]两个 GGF 的卷积,表示的就是“任意选择两个图,将它们的标号合并,并在二者之间任意连边”后的 GGF。
比如,我们可以记录 \(S(x)\) 为序列 \(\{1\}_{n\ge 0}\) 的 GGF,则这里的 \(H(x)\) 即为 \(S^2(x)\)。
小结:
-
连通块就是可以互相到达的等价类,这样的观点可以记录一下。
此后的分析都基于这一点展开,相对来说就不是很难了。
-
后面这个 GGF 有点意思,不知道哪里有更加深入的讲解?
-
最后计算 \(B\) 时,用到的二分图染色的计数性质,也可以记录一下。
代码
#include <cstdio>
#include <algorithm>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
const int mod = 998244353, inv2 = ( mod + 1 ) >> 1;
const int MAXN = ( 1 << 18 ) + 5;
template<typename _T>
void read( _T &x ) {
x = 0; char s = getchar(); int f = 1;
while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
x *= f;
}
template<typename _T>
void write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
inline _T Min( const _T &a, const _T &b ) {
return a < b ? a : b;
}
template<typename _T>
inline _T Max( const _T &a, const _T &b ) {
return a > b ? a : b;
}
int F[MAXN], G[MAXN];
int fac[MAXN], ifac[MAXN], pw2[MAXN][3], pwInv[MAXN][3];
int A[MAXN], B[MAXN], C[MAXN], D[MAXN];
int siz[MAXN];
int N, M;
inline int Qkpow( int, int );
inline int Inv( const int &a ) { return Qkpow( a, mod - 2 ); }
inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }
inline int Qkpow( int base, int indx ) {
int ret = 1;
while( indx ) {
if( indx & 1 ) MulEq( ret, base );
MulEq( base, base ), indx >>= 1;
}
return ret;
}
namespace Basics {
const int L = 18, phi = mod - 1, g = 3;
int w[MAXN];
inline void NTTInit( const int &n = 1 << L ) {
w[0] = 1, w[1] = Qkpow( g, phi >> L );
rep( i, 2, n - 1 ) w[i] = Mul( w[i - 1], w[1] );
}
inline void DIF( int *coe, const int &n ) {
int *wp, p, e, o;
for( int s = n >> 1 ; s ; s >>= 1 )
for( int i = 0 ; i < n ; i += s << 1 ) {
p = ( 1 << L ) / ( s << 1 ), wp = w;
for( int j = 0 ; j < s ; j ++, wp += p ) {
e = coe[i + j], o = coe[i + j + s];
coe[i + j] = Add( e, o );
coe[i + j + s] = Mul( *wp, Sub( e, o ) );
}
}
}
inline void DIT( int *coe, const int &n ) {
int *wp, p, k;
for( int s = 1 ; s < n ; s <<= 1 )
for( int i = 0 ; i < n ; i += s << 1 ) {
p = ( 1 << L ) / ( s << 1 ), wp = w;
for( int j = 0 ; j < s ; j ++, wp += p )
k = Mul( *wp, coe[i + j + s] ),
coe[i + j + s] = Sub( coe[i + j], k ),
coe[i + j] = Add( coe[i + j], k );
}
std :: reverse( coe + 1, coe + n );
int inv = Inv( n ); rep( i, 0, n - 1 ) MulEq( coe[i], inv );
}
inline void PolyDer( int *coe, const int &n ) {
rep( i, 0, n - 2 ) coe[i] = Mul( coe[i + 1], i + 1 );
coe[n - 1] = 0;
}
inline void PolyInt( int *coe, const int &n ) {
per( i, n, 1 ) coe[i] = Mul( coe[i - 1], Inv( i ) );
coe[0] = 0;
}
}
namespace PolyMul {
int P[MAXN], Q[MAXN];
void PolyMul( int *ret, const int *A, const int *B, const int &n ) {
int L; for( L = 1 ; L <= 2 * n - 2 ; L <<= 1 );
rep( i, 0, L - 1 ) P[i] = Q[i] = 0;
rep( i, 0, n - 1 ) P[i] = A[i], Q[i] = B[i];
Basics :: DIF( P, L );
Basics :: DIF( Q, L );
rep( i, 0, L - 1 ) MulEq( P[i], Q[i] );
Basics :: DIT( P, L );
rep( i, 0, n - 1 ) ret[i] = P[i];
}
}
namespace PolyInv {
int P[MAXN], Q[MAXN], U[MAXN], V[MAXN];
void Newton( const int &n ) {
if( n == 1 ) {
U[0] = Inv( P[0] );
return ;
}
int h = ( n + 1 ) >> 1, L; Newton( h );
for( L = 1 ; L <= n + h - 2 ; L <<= 1 );
rep( i, 0, L - 1 ) Q[i] = V[i] = 0;
rep( i, 0, n - 1 ) Q[i] = P[i];
rep( i, 0, h - 1 ) V[i] = U[i];
Basics :: DIF( Q, L );
Basics :: DIF( V, L );
rep( i, 0, L - 1 ) Q[i] = Mul( V[i], Sub( 2, Mul( V[i], Q[i] ) ) );
Basics :: DIT( Q, L );
rep( i, h, n - 1 ) U[i] = Q[i];
}
inline void PolyInv( int *ret, const int *A, const int &n ) {
rep( i, 0, n - 1 ) P[i] = A[i];
Newton( n );
rep( i, 0, n - 1 ) ret[i] = U[i];
}
}
namespace PolyLn {
int P[MAXN], PInv[MAXN];
inline void PolyLn( int *ret, const int *A, const int &n ) {
int L; for( L = 1 ; L <= 2 * n - 3 ; L <<= 1 );
rep( i, 0, L - 1 ) P[i] = PInv[i] = 0;
rep( i, 0, n - 1 ) P[i] = A[i];
PolyInv :: PolyInv( PInv, P, n );
Basics :: PolyDer( P, n );
Basics :: DIF( P, L );
Basics :: DIF( PInv, L );
rep( i, 0, L - 1 ) MulEq( P[i], PInv[i] );
Basics :: DIT( P, L );
Basics :: PolyInt( P, n );
rep( i, 0, n - 1 ) ret[i] = P[i];
}
}
inline void Init( const int &n ) {
fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
rep( i, 0, n ) {
pw2[i][0] = 2;
if( i <= 2 ) pw2[i][i] = 2;
for( int j = 1 ; j < i && j <= 2 ; j ++ )
pw2[i][j] = Mul( pw2[i - 1][j], pw2[i - 1][j - 1] );
for( int j = i + 1 ; j <= 2 ; j ++ )
pw2[i][j] = 1;
pwInv[i][0] = inv2;
if( i <= 2 ) pwInv[i][i] = inv2;
for( int j = 1 ; j < i && j <= 2 ; j ++ )
pwInv[i][j] = Mul( pwInv[i - 1][j], pwInv[i - 1][j - 1] );
for( int j = i + 1 ; j <= 2 ; j ++ )
pwInv[i][j] = 1;
}
}
int main() {
Basics :: NTTInit();
read( N );
rep( i, 1, N ) {
read( siz[i] );
M = Max( M, siz[i] );
}
Init( M );
rep( i, 0, M ) F[i] = Mul( ifac[i], pw2[i][2] );
rep( i, 0, M ) G[i] = Mul( ifac[i], pwInv[i][2] );
PolyMul :: PolyMul( G, G, G, M + 1 );
rep( i, 0, M ) MulEq( G[i], pw2[i][2] );
PolyLn :: PolyLn( G, G, M + 1 );
PolyLn :: PolyLn( F, F, M + 1 );
rep( i, 0, M ) MulEq( G[i], inv2 );
rep( i, 0, M ) A[i] = Mul( ifac[i], pw2[i][2] );
rep( i, 1, M ) B[i] = Mul( i, F[i] );
rep( i, 2, M ) C[i] = G[i];
rep( i, 2, M ) D[i] = Sub( F[i], G[i] );
PolyMul :: PolyMul( B, A, B, M + 1 );
PolyMul :: PolyMul( C, A, C, M + 1 );
PolyMul :: PolyMul( D, A, D, M + 1 );
int val1 = 1, val2 = 1, val3 = 1, val4 = 1;
rep( i, 1, N ) {
int a = Mul( fac[siz[i]], B[siz[i]] ),
b = Mul( siz[i], pw2[siz[i] - 1][2] ),
c = Mul( fac[siz[i]], C[siz[i]] ),
d = Mul( fac[siz[i]], D[siz[i]] );
MulEq( val1, a );
MulEq( val2, Sub( a, b ) );
MulEq( val3, Add( Mul( 2, c ), d ) );
MulEq( val4, d );
}
write( Sub( Add( val1, Mul( inv2, Add( val3, val4 ) ) ), val2 ) ), putchar( '\n' );
return 0;
}