JZOJ5870 【NOIP2018模拟9.15】地图
题目描述
Description
Input
Output
Sample Input
4
2 1 1 2Sample Output
2
Data Constraint
题目大意
一个无向图,给你个点的度数,每个点的度数只有或,问组成这个图的方案数。
解法
思考历程
非常不爽的是,这是一道计数类问题……
首先有个很显然的规律:最终的图只能由简单环或者链组成。
对于一个度数为的点,它一定是某一条链的一端。
对于一个度数为的点,它要么是链中间的某一个点,要么就是环上的一个点。
我们将度点的总数记为,将度点的总数记为。(因为点编号顺序是无论怎样都是等价的)
我首先想到的就是,链的条数是固定的,为。(当然一定是偶数,否则无解)
这条性质,是解题的关键。
所以,试一下DP……
结果因为各种各样的问题爆0
AC解法
其实这题的AC解法很多,DP设的状态五花八门的。
我参考过许多人的方法,但是我并不简单服从,而是想好好推自己的方法,走自己的路。
在我的努力之下,终于AC了。
设表示有个度点放环中的方案数,表示有个度点放链上的方案数。
先考虑前者。
转移分两种:
- 插在某两个相连的点中间
- 用三个点新开一个环。
这样转移时不会出现遗漏的。因为这个点是有自己编号的,它们以一定的顺序插入。并且如果插入的过程不同,那么不可能出现同样的图。
!有一个地方要注意一下,中之所以是,是因为本身必定要放在新环中,所以只能在中选两个,不然会出现重复的情况。
再考虑后者的转移。
只有一种:插在某两个相连的点中间。
除了要计算出两个数组外,还要另外计算中两两匹配的方案数。
考虑使用组合数。
第一次在中选个,第二次在中选个……第次在中选个。
去重,对于每个情况有种排列,所以除掉它。
方案数为
化简得
怎么化简的……自己想去吧……
统计答案时,枚举表示有个2度点。
将和合并在一起,另外乘上两两匹配的方案数,还有中选出个的方案数。
这样时间复杂度可以做到
还能更优?
上面这个时间是求组合数的时间。
然而我们可以预先处理出阶乘来算组合数,除法用逆元,时间
逆元其实可以预处理出来,那么时间
所以这题的范围太小了……
不过我不需要用到阶乘。
考虑对的贡献。
推推式子容易得出
所以我们只预处理出逆元,然后计算组合数时通过上一个推出下一个就好了。
代码
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;
}
总结
如何在计数类问题中,做到不重复,不遗漏才是关键……
组合数大法好……