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);
}