JZOJ5870 【NOIP2018模拟9.15】地图

题目描述

Description

Description

Input

在这里插入图片描述
Output
在这里插入图片描述

Sample Input

4
2 1 1 2

Sample Output

2

Data Constraint

在这里插入图片描述

题目大意

一个无向图,给你NN个点的度数,每个点的度数只有1122,问组成这个图的方案数。


解法

思考历程

非常不爽的是,这是一道计数类问题……
首先有个很显然的规律:最终的图只能由简单环或者链组成。
对于一个度数为11的点,它一定是某一条链的一端。
对于一个度数为22的点,它要么是链中间的某一个点,要么就是环上的一个点。
我们将11度点的总数记为s1s1,将22度点的总数记为s2s2。(因为点编号顺序是无论怎样都是等价的)
我首先想到的就是,链的条数是固定的,为s12\frac{s1}{2}(当然s1s1一定是偶数,否则无解)
这条性质,是解题的关键。
所以,试一下DP……
结果因为各种各样的问题爆0

AC解法

其实这题的AC解法很多,DP设的状态五花八门的。
我参考过许多人的方法,但是我并不简单服从,而是想好好推自己的方法,走自己的路。
在我的努力之下,终于AC了。
fif_i表示有ii22度点放环中的方案数,gig_i表示有ii22度点放链上的方案数。
先考虑前者。
转移分两种:

  1. 插在某两个相连的点中间
  2. 用三个点新开一个环。

这样转移时不会出现遗漏的。因为这ii个点是有自己编号的,它们以一定的顺序插入。并且如果插入的过程不同,那么不可能出现同样的图。
fi=fi1(i1)+fi3C(i1,2)f_i=f_{i-1}*(i-1)+f_{i-3}*C\left(i-1,2\right)
!有一个地方要注意一下,C(i1,2)C\left(i-1,2\right)中之所以是i1i-1,是因为ii本身必定要放在新环中,所以只能在i1i-1中选两个,不然会出现重复的情况。
再考虑后者的转移。
只有一种:插在某两个相连的点中间。
gi=gi1(i1+s12)g_i=g_{i-1}*\left(i-1+\frac{s1}{2}\right)
除了要计算出两个数组外,还要另外计算s1s1中两两匹配的方案数。
考虑使用组合数。
第一次在s1s1中选22个,第二次在s12s1-2中选22个……第s12\frac{s1}{2}次在22中选22个。
去重,对于每个情况有s12!\frac{s1}{2} !种排列,所以除掉它。
方案数为
i=0s11C(s12i,2)s12!\frac{\prod_{i=0}^{s1-1} C\left(s1-2i,2\right)}{\frac{s1}{2} !}
化简得
i=0s11(2i+1)\prod_{i=0}^{s1-1}(2i+1)
怎么化简的……自己想去吧……
统计答案时,枚举ii表示有ii个2度点。
fif_igs2ig_{s2-i}合并在一起,另外乘上两两匹配的方案数,还有s2s2中选出ii个的方案数。
这样时间复杂度可以做到O(N2)O\left(N^2\right)

还能更优?

上面这个时间是求组合数的时间。
然而我们可以预先处理出阶乘来算组合数,除法用逆元,时间O(NlgMOD)O\left(N\lg MOD\right)
逆元其实可以O(N)O\left(N\right)预处理出来,那么时间O(N)O\left(N\right)
所以这题的范围太小了……
不过我不需要用到阶乘。
考虑C(i,s2)C\left(i,s2\right)C(i+1,s2)C\left(i+1,s2\right)的贡献。
推推式子容易得出
C(i,s2)s2ii+1=C(i+1,s2)C\left(i,s2\right)*\frac{s2-i}{i+1}=C\left(i+1,s2\right)
所以我们只预处理出逆元,然后计算组合数时通过上一个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++;
		else
			s2++;
	}
	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;
}

总结

如何在计数类问题中,做到不重复,不遗漏才是关键……
组合数大法好……

posted @ 2018-09-20 12:28  jz_597  阅读(240)  评论(0编辑  收藏  举报