题目描述
题目解法
首先转化题意
如果把每个物种看成一个点
那么对于条件1,即为所有点都连通
对于条件2,即为每个环之间没有共同点
总的来说,题目就是要问有若干个简单环构成的树的方案数
考虑
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 为
i
i
i 个点,
j
j
j 个简单环,每个简单环之间无边的方案数
转移:
d
p
[
i
]
[
j
]
=
∑
k
=
0
i
d
p
[
k
]
[
j
−
1
]
∗
C
i
−
1
k
∗
(
i
−
k
−
1
)
!
2
)
dp[i][j]=\sum^{i}_{k=0}dp[k][j-1]*C^{k}_{i-1}*\frac{(i-k-1)!}{2})
dp[i][j]=∑k=0idp[k][j−1]∗Ci−1k∗2(i−k−1)!)
如何理解?
枚举
k
k
k 为
k
k
k 个点构成
j
−
1
j-1
j−1 个简单环,即第
j
j
j 个简单环有
i
−
k
i-k
i−k 个点
如果单纯从
i
i
i 个点中选
k
k
k 个点的话会算重
所以有一个常用的技巧是固定基准点
考虑枚举
1
1
1 所在的简单环的大小,即为在
i
−
1
i-1
i−1 个点中选
k
k
k 个不在环
1
1
1 中的点,方案数为
C
i
−
1
k
C^{k}_{i-1}
Ci−1k
选好了
k
k
k 个点,我们需要将其排列
总的方案数为
(
i
−
k
)
!
(i-k)!
(i−k)!,因为是圆排列,所以从任意位置开始都可以,从后往前、从前往后算同种方案,所以需要
/
2
/
(
i
−
k
)
/2/(i-k)
/2/(i−k),即为
(
i
−
k
−
1
)
!
2
\frac{(i-k-1)!}{2}
2(i−k−1)!
最后计算答案是可以考虑
p
r
u
f
e
r
prufer
prufer 序列
即有
n
n
n 个点,有
m
m
m 堆,问连
m
−
1
m-1
m−1 条边,使
m
m
m 个堆构成树的方案数
结论:
n
m
−
2
n^{m-2}
nm−2
我们考虑填
m
−
2
m-2
m−2 位,每一位对应原来的点,这条边即为当前点所在堆连向最小无边堆
但是这样会漏
为什么?我们在计算答案时,对于一条无向边(树上的),我们只计算了终点的方案数,未计算起点的方案数
所以最后的答案需要乘上一个每个块的大小
这个可以在
d
p
dp
dp 中统计
所以
d
p
dp
dp 方程就会变为
d
p
[
i
]
[
j
]
=
∑
k
=
0
i
d
p
[
k
]
[
j
−
1
]
∗
C
i
−
1
k
∗
(
i
−
k
)
!
2
)
dp[i][j]=\sum^{i}_{k=0}dp[k][j-1]*C^{k}_{i-1}*\frac{(i-k)!}{2})
dp[i][j]=∑k=0idp[k][j−1]∗Ci−1k∗2(i−k)!),当
i
−
k
=
1
i-k=1
i−k=1 时,需要特判
答案为
(
n
−
1
)
!
2
+
∑
i
=
2
n
d
p
[
n
]
[
i
]
∗
n
i
−
2
\frac{(n-1)!}{2}+\sum^{n}_{i=2}dp[n][i]*n^{i-2}
2(n−1)!+∑i=2ndp[n][i]∗ni−2,因为当只有一个块时,需要特殊计算一下
时间复杂度 O ( n 3 ) O(n^3) O(n3)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N(210);
int n,P,dp[N][N],C[N][N],fac[N];
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
void init(){
C[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=i;j++)
if(!j||j==i) C[i][j]=1;
else C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
fac[0]=fac[1]=1,fac[3]=3;
for(int i=4;i<=n;i++) fac[i]=(LL)fac[i-1]*i%P;
}
int main(){
n=read(),P=read();
init();
dp[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
for(int k=0;k<i;k++)
(dp[i][j]+=(LL)dp[k][j-1]*C[i-1][k]%P*fac[i-k]%P)%=P;
int ans=fac[n-1];
for(int i=2,j=1;i<=n;i++,j=(LL)j*n%P) (ans+=(LL)dp[n][i]*j%P)%=P;
printf("%d",ans);
return 0;
}