题目描述
Description
Input
Output
Sample Input
4
2 1 1 2Sample Output
2
Data Constraint
题目大意
一个无向图,给你 N N N个点的度数,每个点的度数只有 1 1 1或 2 2 2,问组成这个图的方案数。
解法
思考历程
非常不爽的是,这是一道计数类问题……
首先有个很显然的规律:最终的图只能由简单环或者链组成。
对于一个度数为 1 1 1的点,它一定是某一条链的一端。
对于一个度数为 2 2 2的点,它要么是链中间的某一个点,要么就是环上的一个点。
我们将 1 1 1度点的总数记为 s 1 s1 s1,将 2 2 2度点的总数记为 s 2 s2 s2。(因为点编号顺序是无论怎样都是等价的)
我首先想到的就是,链的条数是固定的,为 s 1 2 \frac{s1}{2} 2s1。(当然 s 1 s1 s1一定是偶数,否则无解)
这条性质,是解题的关键。
所以,试一下DP……
结果因为各种各样的问题爆0
AC解法
其实这题的AC解法很多,DP设的状态五花八门的。
我参考过许多人的方法,但是我并不简单服从,而是想好好推自己的方法,走自己的路。
在我的努力之下,终于AC了。
设 f i f_i fi表示有 i i i个 2 2 2度点放环中的方案数, g i g_i gi表示有 i i i个 2 2 2度点放链上的方案数。
先考虑前者。
转移分两种:
- 插在某两个相连的点中间
- 用三个点新开一个环。
这样转移时不会出现遗漏的。因为这 i i i个点是有自己编号的,它们以一定的顺序插入。并且如果插入的过程不同,那么不可能出现同样的图。
f i = f i − 1 ∗ ( i − 1 ) + f i − 3 ∗ C ( i − 1 , 2 ) f_i=f_{i-1}*(i-1)+f_{i-3}*C\left(i-1,2\right) fi=fi−1∗(i−1)+fi−3∗C(i−1,2)
!有一个地方要注意一下, C ( i − 1 , 2 ) C\left(i-1,2\right) C(i−1,2)中之所以是 i − 1 i-1 i−1,是因为 i i i本身必定要放在新环中,所以只能在 i − 1 i-1 i−1中选两个,不然会出现重复的情况。
再考虑后者的转移。
只有一种:插在某两个相连的点中间。
g i = g i − 1 ∗ ( i − 1 + s 1 2 ) g_i=g_{i-1}*\left(i-1+\frac{s1}{2}\right) gi=gi−1∗(i−1+2s1)
除了要计算出两个数组外,还要另外计算 s 1 s1 s1中两两匹配的方案数。
考虑使用组合数。
第一次在 s 1 s1 s1中选 2 2 2个,第二次在 s 1 − 2 s1-2 s1−2中选 2 2 2个……第 s 1 2 \frac{s1}{2} 2s1次在 2 2 2中选 2 2 2个。
去重,对于每个情况有 s 1 2 ! \frac{s1}{2} ! 2s1!种排列,所以除掉它。
方案数为
∏ i = 0 s 1 − 1 C ( s 1 − 2 i , 2 ) s 1 2 ! \frac{\prod_{i=0}^{s1-1} C\left(s1-2i,2\right)}{\frac{s1}{2} !} 2s1!∏i=0s1−1C(s1−2i,2)
化简得
∏ i = 0 s 1 − 1 ( 2 i + 1 ) \prod_{i=0}^{s1-1}(2i+1) i=0∏s1−1(2i+1)
怎么化简的……自己想去吧……
统计答案时,枚举 i i i表示有 i i i个2度点。
将 f i f_i fi和 g s 2 − i g_{s2-i} gs2−i合并在一起,另外乘上两两匹配的方案数,还有 s 2 s2 s2中选出 i i i个的方案数。
这样时间复杂度可以做到 O ( N 2 ) O\left(N^2\right) O(N2)
还能更优?
上面这个时间是求组合数的时间。
然而我们可以预先处理出阶乘来算组合数,除法用逆元,时间 O ( N lg M O D ) O\left(N\lg MOD\right) O(NlgMOD)
逆元其实可以 O ( N ) O\left(N\right) O(N)预处理出来,那么时间 O ( N ) O\left(N\right) O(N)
所以这题的范围太小了……
不过我不需要用到阶乘。
考虑 C ( i , s 2 ) C\left(i,s2\right) C(i,s2)对 C ( i + 1 , s 2 ) C\left(i+1,s2\right) C(i+1,s2)的贡献。
推推式子容易得出
C ( i , s 2 ) ∗ s 2 − i i + 1 = C ( i + 1 , s 2 ) C\left(i,s2\right)*\frac{s2-i}{i+1}=C\left(i+1,s2\right) C(i,s2)∗i+1s2−i=C(i+1,s2)
所以我们只预处理出逆元,然后计算组合数时通过上一个 O ( 1 ) O(1) O(1)推出下一个就好了。
代码
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 2000
#define mod 998244353
int n;
int inv[MAXN+1];
int s1,s2;
int m;
int f[MAXN+1];
int g[MAXN+1];
int main(){freopen("map.in","r",stdin);freopen("map.out","w",stdout);scanf("%d",&n);for (int i=1;i<=n;++i){int x;scanf("%d",&x);if (x==1)s1++;elses2++;}inv[0]=0;inv[1]=1;for (int i=2;i<=s2;++i)inv[i]=(long long)(mod-mod/i)*inv[mod%i]%mod; //O(N)求逆元,具体证明网上大把f[0]=1;for (int i=3;i<=s2;++i)f[i]=((long long)f[i-1]*(i-1)+f[i-3]*(((long long)(i-1)*(i-2)>>1)%mod))%mod;//具体见上f的转移g[0]=1;for (int i=1;i<=s2;++i)g[i]=(long long)g[i-1]*(i-1+(s1>>1))%mod;//具体见上g的转移int matching=1;//表示两两匹配数for (int i=1;i<s1;i+=2)matching=(long long)matching*i%mod;int ans=0,C=1;for (int i=0;i<=s2;++i){ans=(ans+(long long)f[i]*g[s2-i]%mod*C)%mod;//统计答案C=(long long)C*(s2-i)%mod*inv[i+1]%mod;//推出下一个组合数}ans=(long long)ans*matching%mod;//最后乘上两两匹配数printf("%d\n",ans);return 0;
}
总结
如何在计数类问题中,做到不重复,不遗漏才是关键……
组合数大法好……