Problem
Hint
Solution
首先,图中只会存在链和环。 记图中有one个度数为1的点,two个度数为2的点。囿于每条链有两个度数为1的点(链的两端),链的数量是确定的:one2\frac{one}2 2 o n e 。 这时,我灵(nao)光(zi)一(wa)闪(te),想到了一个优(sha)美(bi)的方法。
我的SB方法:递推+组合数学+容斥
观察到链和环的方案相对独立。那么根据乘法原理,我们可以求出链的方案tot1,环的方案tot2,总方案即为tot1*tot2。 那么分类讨论一下。
链的情况
我们先令one个点两两匹配,构成one2\frac{one}2 2 o n e 条只含两个点的链。 不妨枚举当前有n条链。假设增加一条,则点数增加为2*n+2。 枚举点1连接的是哪个点,这有2n+1种可能;而剩下的2 n个点,两两匹配成i条链。 因此,递推式为hn+1=(2∗n+1)∗hnh_{n+1}=(2*n+1)*h_n h n + 1 = ( 2 ∗ n + 1 ) ∗ h n ,其中n为链数。
现在,我们再将度数为2的点插入到这些链中。 设gig_i g i 表示有i个度数为2的点在链中。新插一个点,我们可以插在所有链的非链首节点的左边。譬如下图:
我们可以插在任意一个蓝点左边,因此有3+2=5种方案。实际上,记有k条链,第i条链的链长为lenilen_i l e n i ,插入新点的方案即为∑i=1kleni−1\sum_{i=1}^k len_i-1 ∑ i = 1 k l e n i − 1 。 不难发现,这其实等于已插入的点数 +链数 。 因此,gi=gi−1∗(i−1+one2)g_i=g_{i-1}*(i-1+\frac{one}2) g i = g i − 1 ∗ ( i − 1 + 2 o n e ) ,其中one2\frac{one}2 2 o n e 为链数。
环的情况
环的情况就有些复杂了。 考虑DP。设fi,jf_{i,j} f i , j 表示i个度数为2的点在环上,其中有j个一元环的方案数。 囿于原图不存在自环,我们最终得到的应是fi,0f_{i,0} f i , 0 。 然后可以得到三种转移:1.新建一个一元环;2.令当前点加入到一个一元环中;3.令当前点加入到一个多元环中。
然而还有一个坑点——那就是环翻转一下,和原来全等,但是我们会算重。 不妨在新建环的时候,就将其贡献记为12\frac 12 2 1 ,这样最终算出的结果便是去了重的。 然而,我们这样只能算出无自环的方案数,不能算出无二元环(重边)的方案数。
考虑容斥。 枚举有i个度数为2的点在环上,其中有j个二元环。那么正负性为(−1)j(-1)^j ( − 1 ) j ,系数为Ci2j∗hj∗2−jC_i^{2j}*h_j*2^{-j} C i 2 j ∗ h j ∗ 2 − j ,其中hjh_j h j 为上述提到的,点两两匹配的方案数。 系数中有个2−j2^{-j} 2 − j 的原因是我们把二元环的贡献都算成了12\frac 12 2 1 (建环时是12\frac 12 2 1 ,再插一个点是1,12∗1=12\frac 12*1=\frac 12 2 1 ∗ 1 = 2 1 ),然而二元环的贡献应是1;于是在去掉二元环的方案中,我们应该也乘回这些12\frac 12 2 1 以弥补二元环的缺失。 然后对于每个i,都求一波∑j=0⌊i2⌋(−1)j∗Ci2j∗hj∗2−j∗fi−2j,0\sum_{j=0}^{\lfloor \frac i2 \rfloor} (-1)^j*C_i^{2j}*h_j*2^{-j}*f_{i-2j,0} ∑ j = 0 ⌊ 2 i ⌋ ( − 1 ) j ∗ C i 2 j ∗ h j ∗ 2 − j ∗ f i − 2 j , 0 ,而这便是真正的fif_i f i (无自环、无二元环的方案数)。
Code
#include <bits/stdc++.h>
#define P(x,y) x=((x)+(y))%mo
#define T(x,y) x=((x)*(y))%mo
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int N= 4001 ;
const ll mo= 998244353 ;
int k, n, d, one, two;
ll i, j, ls, dou[ N] , lian[ N] , div2, f[ N] [ N] , g, xs, C[ N] [ N] , zf, ans;
ll fpow ( ll x, ll y)
{
ll ans= 1 ;
for ( ; y; y>>= 1 , T ( x, x) ) if ( y& 1 ) T ( ans, x) ;
return ans;
}
int main ( )
{
freopen ( "map.in" , "r" , stdin ) ;
freopen ( "map.out" , "w" , stdout ) ;
scanf ( "%d" , & n) ;
fo ( i, 1 , n)
{
scanf ( "%d" , & d) ;
d& 1 ? one++ : two++ ;
}
if ( one& 1 ) { puts ( "0" ) ; return 0 ; }
dou[ 0 ] = 1 ;
fo ( i, 1 , n) dou[ i] = dou[ i- 1 ] * ( i* 2 - 1 ) % mo;
lian[ 0 ] = dou[ ls= one>> 1 ] ;
fo ( i, 1 , two) lian[ i] = lian[ i- 1 ] * ( ls+ i- 1 ) % mo;
f[ 0 ] [ 0 ] = 1 ; div2= fpow ( 2 , mo- 2 ) ;
fo ( i, 0 , two- 1 )
fo ( j, 0 , i)
if ( f[ i] [ j] )
{
P ( f[ i+ 1 ] [ j+ 1 ] , f[ i] [ j] * div2) ;
P ( f[ i+ 1 ] [ j] , f[ i] [ j] * ( i- j) ) ;
if ( j) P ( f[ i+ 1 ] [ j- 1 ] , f[ i] [ j] * j) ;
}
fo ( i, 0 , two)
{
C[ i] [ 0 ] = 1 ;
fo ( j, 1 , i) C[ i] [ j] = ( C[ i- 1 ] [ j- 1 ] + C[ i- 1 ] [ j] ) % mo;
}
fo ( i, 0 , two)
{
g= 0 ;
fo ( j, 0 , i>> 1 )
{
xs= C[ i] [ j<< 1 ] * dou[ j] % mo* fpow ( div2, j) % mo;
zf= ( j& 1 ? - 1 : 1 ) ;
P ( g, zf* xs* f[ i- j* 2 ] [ 0 ] ) ; P ( g, mo) ;
}
P ( ans, g* C[ two] [ i] % mo* lian[ two- i] ) ;
}
printf ( "%lld" , ans) ;
}
一个更为舒服的方法
实际上,这道题一个DP就解决了。 设fi,jf_{i,j} f i , j 表示i个度数为2的点,其中j个点在环上(即剩下的i-j个点在链上)的方案数。 有以下三种转移:
fi,j={fi−3,j−3∗Ci−12新建三元环fi−1,j−1∗(j−1)将点i插入一个环中fi−1,j ∗(i−j−1+one2)将点i插入一条链中 f_{i,j}=\left\{
\begin{aligned}
&f_{i-3,j-3} *C_{i-1}^2 & & 新建三元环 \\
&f_{i-1,j-1} *(j-1) & & 将点i插入一个环中 \\
&f_{i-1,j} \ \ \ *(i-j-1+\frac{one}2) & & 将点i插入一条链中
\end{aligned}
\right.
f i , j = ⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ f i − 3 , j − 3 ∗ C i − 1 2 f i − 1 , j − 1 ∗ ( j − 1 ) f i − 1 , j ∗ ( i − j − 1 + 2 o n e ) 新 建 三 元 环 将 点 i 插 入 一 个 环 中 将 点 i 插 入 一 条 链 中 新建环的贡献为何是Ci−12C_{i-1}^2 C i − 1 2 呢?我们假定点i就在新环内 ,然后从剩下的i-1个点中选2个出来陪它。如若不然,则有可能算重。然后不必再因会翻转除以2,因为三元环定是唯一的。
这样的话,ans=∑i=0twoftwo,ians=\sum_{i=0}^{two} f_{two,i} a n s = ∑ i = 0 t w o f t w o , i ,其中two为度数为2的节点的个数。 但还没把链首、链尾两两匹配的方案数算上,所以最后要再乘上。 时间复杂度:O(n2)O(n^2) O ( n 2 ) 。
Code
#include <bits/stdc++.h>
#define P(x,y) x=(x+y)%mo
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int N= 2001 ;
const ll mo= 998244353 ;
int n, d, two;
ll i, j, one, f[ N] [ N] , ans;
int main ( )
{
freopen ( "map.in" , "r" , stdin ) ;
freopen ( "map.out" , "w" , stdout ) ;
scanf ( "%d" , & n) ;
fo ( i, 1 , n)
{
scanf ( "%d" , & d) ;
d& 1 ? one++ : two++ ;
}
if ( one& 1 ) { puts ( "0" ) ; return 0 ; }
f[ 0 ] [ 0 ] = 1 ;
fo ( i, 1 , two)
fo ( j, 0 , i)
{
if ( j>= 3 ) f[ i] [ j] = f[ i- 3 ] [ j- 3 ] * ( ( i- 1 ) * ( i- 2 ) >> 1 ) % mo;
if ( j>= 1 ) P ( f[ i] [ j] , f[ i- 1 ] [ j- 1 ] * ( j- 1 ) ) ;
P ( f[ i] [ j] , f[ i- 1 ] [ j] * ( one/ 2 + i- j- 1 ) ) ;
}
fo ( i, 0 , two) P ( ans, f[ two] [ i] ) ;
fo ( i, 3 , one) if ( i& 1 ) ( ans* = i) % = mo;
printf ( "%lld" , ans) ;
}