Problem
Hint
Solution
- 首先,圖中只會存在鏈和環。
- 記圖中有one個度數爲1的點,two個度數爲2的點。囿於每條鏈有兩個度數爲1的點(鏈的兩端),鏈的數量是確定的:2one。
- 這時,我靈(nao)光(zi)一(wa)閃(te),想到了一個優(sha)美(bi)的方法。
我的SB方法:遞推+組合數學+容斥
- 觀察到鏈和環的方案相對獨立。那麼根據乘法原理,我們可以求出鏈的方案tot1,環的方案tot2,總方案即爲tot1*tot2。
- 那麼分類討論一下。
鏈的情況
- 我們先令one個點兩兩匹配,構成2one條只含兩個點的鏈。
- 不妨枚舉當前有n條鏈。假設增加一條,則點數增加爲2*n+2。
- 枚舉點1連接的是哪個點,這有2n+1種可能;而剩下的2n個點,兩兩匹配成i條鏈。
- 因此,遞推式爲hn+1=(2∗n+1)∗hn,其中n爲鏈數。
- 現在,我們再將度數爲2的點插入到這些鏈中。
- 設gi表示有i個度數爲2的點在鏈中。新插一個點,我們可以插在所有鏈的非鏈首節點的左邊。譬如下圖:
- 我們可以插在任意一個藍點左邊,因此有3+2=5種方案。實際上,記有k條鏈,第i條鏈的鏈長爲leni,插入新點的方案即爲∑i=1kleni−1。 不難發現,這其實等於已插入的點數+鏈數。
- 因此,gi=gi−1∗(i−1+2one),其中2one爲鏈數。
環的情況
- 環的情況就有些複雜了。
- 考慮DP。設fi,j表示i個度數爲2的點在環上,其中有j個一元環的方案數。
- 囿於原圖不存在自環,我們最終得到的應是fi,0。
- 然後可以得到三種轉移:1.新建一個一元環;2.令當前點加入到一個一元環中;3.令當前點加入到一個多元環中。
- 然而還有一個坑點——那就是環翻轉一下,和原來全等,但是我們會算重。
- 不妨在新建環的時候,就將其貢獻記爲21,這樣最終算出的結果便是去了重的。
- 然而,我們這樣只能算出無自環的方案數,不能算出無二元環(重邊)的方案數。
- 考慮容斥。
- 枚舉有i個度數爲2的點在環上,其中有j個二元環。那麼正負性爲(−1)j,係數爲Ci2j∗hj∗2−j,其中hj爲上述提到的,點兩兩匹配的方案數。
- 係數中有個2−j的原因是我們把二元環的貢獻都算成了21(建環時是21,再插一個點是1,21∗1=21),然而二元環的貢獻應是1;於是在去掉二元環的方案中,我們應該也乘回這些21以彌補二元環的缺失。
- 然後對於每個i,都求一波∑j=0⌊2i⌋(−1)j∗Ci2j∗hj∗2−j∗fi−2j,0,而這便是真正的fi(無自環、無二元環的方案數)。
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,j表示i個度數爲2的點,其中j個點在環上(即剩下的i-j個點在鏈上)的方案數。
- 有以下三種轉移:
fi,j=⎩⎪⎪⎨⎪⎪⎧fi−3,j−3∗Ci−12fi−1,j−1∗(j−1)fi−1,j ∗(i−j−1+2one)新建三元環將點i插入一個環中將點i插入一條鏈中
- 新建環的貢獻爲何是Ci−12呢?我們假定點i就在新環內,然後從剩下的i-1個點中選2個出來陪它。如若不然,則有可能算重。然後不必再因會翻轉除以2,因爲三元環定是唯一的。
- 這樣的話,ans=∑i=0twoftwo,i,其中two爲度數爲2的節點的個數。
- 但還沒把鏈首、鏈尾兩兩匹配的方案數算上,所以最後要再乘上。
- 時間複雜度:O(n2)。
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);
}