题目
题目描述
鸽子
α
\alpha
α 最近把
n
n
n 根棒棒糖拆了开来,正准备拼到一起…
α α α 现在十分苦恼,它现在需要把这 n n n 根糖拆出来的 n n n 个糖果和 n n n 根棒给重新组合一下,不过 α α α 并不知道该怎么组装好看,只能先列出几个条件:
- 所有糖果和棒都要用上。
- 每对糖果之间都必须直接或间接连接。
- 显然对于一个棒全部用上的情况,会形成一个环, α α α 觉得环上点的数量为 m m m 比较好。
现在 α α α 想问的是,有多少种满足上述几个条件的无向图呢?
注意,包在糖果上的包装纸上才有标号,而棒上是没有的。两个方案不同当且仅当对于某一个标号的糖果,在两个方案中与它相连的糖果的标号组成的集合不同,并且我们定义每个点的集合在一开始就有它本身(即形成自环的点不会影响方案数)。
答案对 998244353 998244353 998244353 取模。
输入格式
输入包括一行两个正整数
n
n
n 和
m
m
m 。
输出格式
输出一行一个整数,表示方案数。
数据范围与约定
对于所有数据,
1
≤
m
≤
n
≤
3
×
1
0
3
1\le m\le n\le 3\times10^3
1≤m≤n≤3×103 。
思路
s u b t a s k 1 ( m = 1 ∨ m = 2 ) {\tt subtask\;1}(m=1\vee m=2) subtask1(m=1∨m=2)
发现就是求本质不同的树的数量。设 f ( n ) f(n) f(n) 为 n n n 个点时本质不同的树的数量。显然 f ( 1 ) = 1 f(1)=1 f(1)=1 。有递推方程式:
f ( n ) = ∑ i = 1 n − 1 ( n − 2 i − 1 ) i ⋅ f ( i ) f ( n − i ) f(n)=\sum_{i=1}^{n-1}{n-2\choose i-1}i\cdot f(i)f(n-i) f(n)=i=1∑n−1(i−1n−2)i⋅f(i)f(n−i)
为什么是这样呢?发现我们的做法是 拆掉一条边再重新接上,为了不算重,我们规定把 1 1 1 作为树根,断开 n n n 与其父节点之间的连边。步骤如下:
- 在除了 1 1 1 和 n n n 以外的 n − 2 n{\tt -}2 n−2 个点中选 i − 1 i{\tt -}1 i−1 个点,与 1 1 1 一起组成 i i i 个点的树。方案 ( n − 2 i − 1 ) f ( i ) {n-2\choose i-1}f(i) (i−1n−2)f(i) 。
- 剩下的 n − i n{\tt-}i n−i 个点组成一棵树,以 n n n 作为根节点。方案数为 f ( n − i ) f(n-i) f(n−i) 。
- 选取 i i i 个点中的任意一个作为 n n n 的父节点。方案数为 i i i 。
乘法原理可得该递推式。
2019 / 11 / 28 u p d a t e {\tt 2019/11/28\; update} 2019/11/28update
只需要使用 p r u f e r \tt{prufer} prufer 序列就可以直接得到 f n = n n − 2 f_n=n^{n-2} fn=nn−2 的方便打法——当然也可以用递推的方式验证。
果然我这种dp又差、学的又少的人就会被淘汰。
s u b t a s k ∞ \tt{subtask\;\infty} subtask∞
模仿上面的想法,发现只改变点的数量,所以考虑用 g ( n ) g(n) g(n) 表示 n n n 个点组成环上有 m m m 个点的本质不同的基环树的数量( m m m 即输入中的 m m m )。边界条件 g ( m ) = ( m − 1 ) ! 2 g(m)=\frac{(m-1)!}{2} g(m)=2(m−1)! ,递推式
g ( n ) = n n − m ∑ i = m n − 1 ( n − 1 i ) i g ( i ) f ( n − i ) g(n)=\frac{n}{n-m}\sum_{i=m}^{n-1}{n-1\choose i}i\;g(i)\;f(n-i) g(n)=n−mni=m∑n−1(in−1)ig(i)f(n−i)
初状态
g
(
m
)
g(m)
g(m) 就是每个点都在环上。考虑从
1
1
1 开始按顺序生成一个序列,有
(
m
−
1
)
!
(m{\tt-}1)!
(m−1)! 种方案。但是顺时针和逆时针实际上是一样的,所以再除以
2
2
2 。即:
1
−
2
−
3
−
4
1{\tt-}2{\tt-}3{\tt-}4
1−2−3−4 和
1
−
4
−
3
−
2
1{\tt-}4{\tt-}3{\tt-}2
1−4−3−2 本质是一个环。画一画就知道了!
题外话:我最近在一本数学奥赛、讲组合数学专题的书上看到了这个玩意儿(下图左一)
其他的情况,还是拆掉一条边吧:
- 若 1 1 1 不在环上,把环上的点都视做树根,拆掉 1 1 1 与其父节点的连边。那么就得从除去 1 1 1 的 ( n − 1 ) (n{\tt-}1) (n−1) 个点中选 i i i 个点组成起始基环树,然后把 1 1 1 接上去(并不是一定要接到环上去,不然会算掉 1 1 1 不与环直接相连的情况)。答案是 X = ∑ i = m n − 1 ( n − 1 i ) i g ( i ) f ( n − i ) X=\sum_{i=m}^{n-1}{n-1\choose i}i\;g(i)\;f(n{\tt-}i) X=∑i=mn−1(in−1)ig(i)f(n−i) 。
- 1 1 1 在环上的情况,将环上的点的点集记作 S S S ,就 s w a p ( 1 , v ) ( v ∈ S ) {\tt swap}(1,v)(v\in S) swap(1,v)(v∈S) 一下就行了!所以是 m X mX mX 。听上去很合理。
可是 m X mX mX 有重复的情况啊!
考虑一个
1
1
1 在环上的情况,发现它的“前身”可以是
1
1
1 与非环上的
n
−
m
n{\tt-}m
n−m 个点中任意一个点
s
w
a
p
\tt{swap}
swap 得到的结果。
比如楼上的方案。它可以是这两种情况:
也就是说,每
n
−
m
n{\tt-}m
n−m 个方案都是一样的!所以只有
m
X
÷
(
n
−
m
)
=
m
n
−
m
X
mX\div (n-m)=\frac{m}{n-m}X
mX÷(n−m)=n−mmX 种情况。
由加法原理, a n s = m n − m X + X = m + ( n − m ) n − m X = n n − m X {\tt ans}=\frac{m}{n-m}X+X=\frac{m+(n-m)}{n-m}X=\frac{n}{n-m}X ans=n−mmX+X=n−mm+(n−m)X=n−mnX 。
代码
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
const int MaxN = 3000, Mod = 998244353;
int f[MaxN+5], g[MaxN+5], c[MaxN][MaxN], n, m, inv[MaxN+5];
int main(){
scanf("%d %d",&n,&m);
for(int i=(inv[1]=1)+1; i<=n; ++i)
inv[i] = (0ll+Mod-Mod/i)*inv[Mod%i]%Mod;
for(int i=0; i<n; ++i) // 杨辉三角
for(int j=c[i][0]=1; j<=i; ++j)
c[i][j] = (c[i-1][j]+c[i-1][j-1])%Mod;
f[1] = 1;
for(int i=2; i<=n; ++i) for(int j=1; j<i; ++j)
f[i] = (1ll*j*f[j]%Mod*f[i-j]%Mod*c[i-2][j-1]+f[i])%Mod;
// 或者用f[i] = qkpow(i,i-2,Mod)也可以
if(m == 1 or m == 2){
printf("%d",f[n]);
return 0;
}
g[m] = 1;
for(int i=3; i<m; ++i) // (m-1)!/2
g[m] = (1ll*g[m]*i)%Mod;
for(int i=m+1; i<=n; ++i){
for(int j=m; j<i; ++j)
g[i] = (1ll*j*g[j]%Mod*f[i-j]%Mod*c[i-1][j]+g[i])%Mod;
g[i] = 1ll*g[i]*i%Mod*inv[i-m]%Mod;
}
printf("%d",g[n]);
return 0;
}