说在前面
并没有什么想说的,但是要保持格式=w=
题目
题目大意
N
N
个点的「带环的树」计数。环与环不能共点,树不能有重边。答案对 取模
N≤200,M≤106
N
≤
200
,
M
≤
10
6
输入输出格式
输入格式:
一行两个数字
N,M
N
,
M
输出格式:
输出一个数字表示答案
解法
(很早之前就见过这道题,题面很赞hhhh,然而me并不会于是去看的题解)
题目让我们统计,环不共点的带环树的数量。带环树肯定不好统计,于是想办法向树转化
我们可以这么想这棵带环树,这棵树相当于是由一些「点块」拼接起来的。「点块」,要么是单个点,要么是一个环
那么「点块」之间的拼接方案,也就是
n
n
个点形成了 个块的生成树计数。由prufer 我们可以知道,
n
n
个点的生成树计数是 ,类比过来可以知道,
n
n
个点 个块的生成树数量是
nm−2
n
m
−
2
(关于Prufer下面有补充)
那么现在处理完了块与块之间连接的方案数,再来考虑分块的方案数
定义
dp[i][j]
d
p
[
i
]
[
j
]
表示,把前
i
i
个点分成 块的方案数。考虑转移,与图的dp类似,枚举当前块的大小,然后考虑有哪些点和当前点在同一个环,可得转移:
dp[i][j]=∑dp[i−k][j−1]∗Ck−1i−1∗f[k]
d
p
[
i
]
[
j
]
=
∑
d
p
[
i
−
k
]
[
j
−
1
]
∗
C
i
−
1
k
−
1
∗
f
[
k
]
其中
f[k]
f
[
k
]
是
k
k
个点组成的环的方案数,,特别的
f[2]=0
f
[
2
]
=
0
(因为不存在大小为2的块)
Ck−1i−1 C i − 1 k − 1 就是,除了当前点之外,从剩下的 i−1 i − 1 个点选 k−1 k − 1 个点,与当前点构成一个环的方案数。然后使用 f[k] f [ k ] 给选出来的这些点分配标号,首先是个环排列,但是这个环是可以对称的,于是有 (k−1)!2 ( k − 1 ) ! 2 ,再加上环的朝向有 k k 种,所以得出
关于环的朝向问题,比较抽象,建议多想一想(比如下图,环之间的连接方式相同,编号相同,但是朝向不同):
另外,对于
n
n
个点都在一个环上的情况,需要单独统计
然后这题就做完了
关于Prufer的一点补充(附Matrix67传送门)
每一棵 个节点的树都可以唯一的对应到
n−2
n
−
2
长度的prufer上去,而每个Prufer都一定可以还原到一棵树上来,也即每一个Prufer编码都和一棵树唯一对应。又因为Prufer编码的
n−2
n
−
2
个数都可以取
1
1
到 中的任意一个数字,因此
n
n
个节点的树有 种。(这道题也是一样的,每次去掉一个块,因此只有
m−2
m
−
2
个数字,但是数字可以在
1
1
到 中任取)
Q:为什么编码长度不能是
n−1
n
−
1
呢?这样的话岂不是就有
nn−1
n
n
−
1
种了?
A:其实,当已经编码了前
n−2
n
−
2
个数字的时候,第
n−1
n
−
1
个数字就已经唯一确定了(因为根据编码过程,第
n−1
n
−
1
个数字一定是剩下的较小的那个数字,所以长度为
n−2
n
−
2
就已经足够了)
下面是自带大常数的代码
随手写了一发Rk1了= =
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , P ;
long long dp[205][205] , C[205][205] , hp[205] , ans ;
void preWork(){
for( int i = 0 ; i <= N ; i ++ ){
C[i][0] = 1 ;
for( int j = 1 ; j <= i ; j ++ )
C[i][j] = ( C[i-1][j-1] + C[i-1][j] ) %P ;
} hp[1] = 1 , hp[3] = 3 ;
for( int i = 4 ; i <= N ; i ++ ) hp[i] = hp[i-1] * i %P ;
}
void solve(){
dp[0][0] = 1 ;
short i , j , k ;
for( i = 1 ; i <= N ; i ++ ){ // 点数
for( j = 1 ; j <= i ; j ++ ){ // 块数
for( k = 1 ; k <= i - j + 1 ; k ++ ) // 当前环
dp[i][j] += dp[i-k][j-1] * hp[k] * C[i-1][k-1]%P ;
dp[i][j] %= P ;
// printf( "dp[%d][%d] = %lld\n" , i , j , dp[i][j] ) ;
}
} ans = hp[N-1] ; // 单独累计只有一个环的情况
long long tmp = 1 ;
for( int i = 2 ; i <= N ; i ++ ){
ans += dp[N][i] * tmp ;
tmp = tmp * N %P ;
} printf( "%lld" , ans %P ) ;
}
int main(){
scanf( "%d%d" , &N , &P ) ;
preWork() ; solve() ;
}