JZOJ5870 【NOIP2018模拟9.15】地图

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/A1847225889/article/details/82779095

题目描述

Description

Description

Input

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

Sample Input

4
2 1 1 2

Sample Output

2

Data Constraint

在这里插入图片描述

题目大意

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


解法

思考历程

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

AC解法

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

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

这样转移时不会出现遗漏的。因为这 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)
!有一个地方要注意一下, C ( i 1 , 2 ) C\left(i-1,2\right) 中之所以是 i 1 i-1 ,是因为 i i 本身必定要放在新环中,所以只能在 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)
除了要计算出两个数组外,还要另外计算 s 1 s1 中两两匹配的方案数。
考虑使用组合数。
第一次在 s 1 s1 中选 2 2 个,第二次在 s 1 2 s1-2 中选 2 2 个……第 s 1 2 \frac{s1}{2} 次在 2 2 中选 2 2 个。
去重,对于每个情况有 s 1 2 ! \frac{s1}{2} ! 种排列,所以除掉它。
方案数为
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} !}
化简得
i = 0 s 1 1 ( 2 i + 1 ) \prod_{i=0}^{s1-1}(2i+1)
怎么化简的……自己想去吧……
统计答案时,枚举 i i 表示有 i i 个2度点。
f i f_i g s 2 i g_{s2-i} 合并在一起,另外乘上两两匹配的方案数,还有 s 2 s2 中选出 i i 个的方案数。
这样时间复杂度可以做到 O ( N 2 ) O\left(N^2\right)

还能更优?

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

总结

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

扫描二维码关注公众号,回复: 3343900 查看本文章

猜你喜欢

转载自blog.csdn.net/A1847225889/article/details/82779095