這題有好幾個作法,這裏介紹兩個spa
(這彷佛是網上的正解)
From zsjzliziyang.net
設f[ i ][ j ]表示度數爲一、2的點個數分別爲i,j時答案。
分爲下面的4種狀況
1:當t2爲0時咱們新增若干條長度爲2的鏈,咱們將不會在這些鏈中間加其餘任何點
2:新增形如1-2-1的一條新鏈
3:在條鏈的一個位置加入兩個爲2的點
4:把某一條鏈拆掉,將其中度數爲2的點與3個新的點構成一個新的環
這個方法最關鍵的地方就是它是把一條鏈中間的度數爲2的點和3個新的點構成一個環,很大程度上避免了在環中間加入新的數致使很難搞重複的窘境code
而後進行DP便可
但由於本題有更快的方法,因此做者未打出來
詳情可轉至zsjzliziyang的文章blog
這解法我仍是打了的[小慶幸]ip
因爲點的度數只有1,2兩種,圖的組成必定是若干條鏈與若干個環,設度數爲1,2點的個數分別爲c1,c2。get
一條鏈兩端有兩個度數爲1的點,因而鏈的條數k肯定了k=c1/2(c1爲奇數則無解)。input
首先考慮c1個點兩兩匹配的方案數,考慮將c1個點放到左右兩邊對齊的方案,即A(c1,c1/2),而後對齊的一對點互相交換是同一種方案,即每種方案算重複2^(c1/2)次。綜上c1個點兩兩匹配方案數爲A(c1,c1/2)/(2^(c1/2))。it
而後考慮c2個點怎麼分配。枚舉x表示分配x個點組成鏈,c2-x個點組成環,先乘上係數C(c2,x)。io
設f[i]表示i個點插進k條鏈的方案,只需考慮i有幾個位置可插。以前已經插入i-1個點,所以有i-1+k個位置可插,遞推式:class
f[0]=1
f[i]=f[i-1]*(i-1+k)
設g[i]表示i個點組成若干環的方案,分兩種狀況:1.i與前面兩點新建三元環 2.i插入到以前某個環 遞推式:
g[0]=1
g[i]=g[i-3]C(i-1,2)+g[i-1](i-1)
最終答案即爲:
sigma(f[x]g[c2-x]C(c2,x))
預處理階乘逆元,組合數能夠O(1)算出,f,g能夠O(n)遞推,答案也可O(n)計算,總複雜度O(n)
#include <cstdio> #include <algorithm> #define MO 998244353 #define N 2001 #define open(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout); using namespace std; int i,n,a; long long ans,t,tot,p[3],f[N],g[N],inv[5001],inc[5001]; long long ksm(long long x,int y) { long long sum=1; while (y) { if (y&1)sum=sum*x%MO; x=x*x%MO; y>>=1; } return sum; } long long C(long long x,long long y) { return inv[x]*inc[y]%MO*inc[x-y]%MO; } int main() { open("graph"); inv[1]=inv[0]=1; for (i=2;i<=5000;i++) inv[i]=inv[i-1]*i%MO; inc[5000]=ksm(inv[5000],MO-2); for (i=4999;i>=0;i--) inc[i]=inc[i+1]*(i+1)%MO; scanf("%d",&n); for (i=1;i<=n;i++) { scanf("%d",&a); p[a]++; } tot=p[1]/2; if (p[1]%2!=0) { printf("0"); return 0; } t=inc[tot]*inv[p[1]]%MO*ksm(ksm(2,tot),MO-2)%MO; f[0]=g[0]=1; for (i=1;i<=p[2];i++) { f[i]=f[i-1]*(i-1+tot)%MO; g[i]=g[i-1]*(i-1)%MO; if (i>=3) g[i]=(g[i]+g[i-3]*C(i-1,2))%MO; } for (i=0;i<=p[2];i++) ans=(ans+f[i]*g[p[2]-i]%MO*C(p[2],i)%MO)%MO; printf("%lld",t*ans%MO); return 0; }