【题解】CJOI2019 登峰造鸡境 (Prufer序列+斯特林数)
题目背景
舒服了。
题目描述
你有一颗n个点的无根树,每个点有有一个标号(1~n)。
现在你知道,总共有m个叶子节点,求不同的树的形态方案数。
答案对\(10^9+7\)取模。下面是一些可能有用的定义:
叶子:度数为1的点。
不同:若对于两颗标号相同的树\(T1=(V,E_1),T2=(V,E_2)\),\(T1\neq T2\)当且仅当存在\((u,v) \in E_1 ,(u,v) \notin E_2\)输入格式
一共一行,第一行包含两个数n,m分别表示点的总个数和叶子数。
数据保证树一定存在。输出格式
一行一个整数,输出答案对\(10^9+7\)取模的结果。
输入样例1
5 3
输出样例1
60
子任务
对于\(10\%\)的数据,保证\(n,m<=5\)
对于\(20\%\)的数据,保证\(n,m<=10\)
对于\(40\%\)的数据,保证\(n,m<=20\)
对于\(60\%\)的数据,保证\(n,m<=5000\)
对于另外\(10\%\)的数据,保证\(m=2\)
对于另外\(10\%\)的数据,保证\(m=n-1\)
对于另外\(10\%\)的数据,保证\(m>=n-5\)
对于\(100\%\)的数据,保证\(n,m<=2\times 10^5\)
\(Solution\)
树的计数问题先通过一一对应转换为Prufer序列,再根据Prufer序列和第二类斯特林数求解。
Prufer序列
假设得到一颗有标号的树\(T\),我们通过这样的操作可以得到一个序列,这个序列和它对应的树是一一对应的。也就是说,任何两个不同的合法的Prufer序列都会对应出不同的两颗树。注意到这里的树是带编号的。
在树中,选取一个编号最小的叶子节点,将它的父亲节点加入Prufer序列,并且将这个叶子节点删去。
直到只剩下两个节点为止(只有一条边没有确定了),此时已经可以确定整个树的形态了。
那么得到了一个个数是\(n-2\)个的序列,这个序列和树的形态一一对应。那么这\(n-2\)个元素的序列可以构成
\[ n^{n-2} \]
种组合。
根据一一对应法则,也就是说有n个不带标号节点的树总共有\(n^{n-2}\)种组合。
我们看一下这个序列的意义,一个节点在Prufer序列里出现的次数就是它的度数-1。那么现在问题就变成了,我要保证\(m\)个节点在Prufer序列里不出现。
第二类斯特林数
\(\begin{Bmatrix}n\\m\end{Bmatrix}\)表示\(n\)个元素划分为\(m\)个非空集合的方案数。
这里蕴藏的信息是:元素有区别,集合无区别。
递推公式
\[ {n \brace m}={n-1 \brace m-1}+m{n-1 \brace m} \]
证明:见博客
容斥\(O(n)\)或者NTT\(O(n\log n)\)求一列
\[ S_2(n,m)=\begin{Bmatrix}n\\m\end{Bmatrix}=\frac 1 {m!} \sum_{i=0}^m (-1)^i{(m-i)^n}{m\choose i} \]
证明:见博客
最后的答案
\[ (n-m)!\times{n\choose m}\times{n-2\brace n-m} \]
因为斯特林数最后的盒子(集合)没有区别,然而我们这里是有区别的,所以应该乘上\((n-m)!\)补回来。
//@winlere
#include
#include
#include
#include
using namespace std; typedef long long ll;
inline int qr(){
register int ret=0,f=0;
register char c=getchar();
while(c<48||c>57)f|=c==45,c=getchar();
while(c>=48&&c<=57) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;
}
const int mod=1e9+7;
const int maxn=2e5+5;
int jc[maxn];
int inv[maxn];
int n,m,ans,k;
typedef const int& ct;
inline int ksm(int base,ct p){
register int ret=1;
for(register int t=p;t;t>>=1,base=1ll*base*base%mod)
if(t&1) ret=1ll*ret*base%mod;
return ret;
}
int C(int n,int m){
if(n