dp+prufer 序列 2020牛客暑期多校训练营(第七场)Valuable Forests
题目大意:
题解:
这个题目首先要知道无根树计数-prufer序列,学习来源 无根树的计数——prufer序列
然后就是一点点思维+dp
- 通过prufer序列可以知道,对于一个n大小的无根树,不同的树有 (n^{n-2}) (其实就是n-2个点,每个点都有n种可能)
- 本题要求无根树所有点的度数的平方和,prufer的一个重要性质就是序列中某个编号出现的次数就是这个编号节点的度数-1,所以对于一个大小为n的树,有n-2个格子可以放数字进去,先枚举1的数量,求出1的权值,再枚举2的数量,求出2节点的权值,以此类推,可以求出大小是n可以构成的所有无根树的权值和。
- 第二点求出了大小为n可以构成的所有无根树的权值之和,但是题目要求大小为n的所有森林的权值和。这个可以定一个 (res[i]) 表示大小为 (i) 的森林无根树的组成方案。
- 假设每次放入的一个点一定是新的一棵树,所以枚举每次放入一个点可以构成的树的大小。
- 转移方程:$res[i] += res[i-j]C_{i-1}^{j-1}j^{j-2} $
#include <bits/stdc++.h>
#define debug(x) cout<<"debug:"<<#x<<" = "<<x<<endl;
using namespace std;
typedef long long ll;
const int maxn = 5e3+10;
ll dp[maxn],res[maxn],c[maxn][maxn],f[maxn];
int main() {
int T, mod;
scanf("%d%d", &T, &mod);
for (int i = 0; i < maxn; i++) {
c[i][0] = 1;
for (int j = 1; j < maxn; j++) {
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
}
for (int i = 2; i < maxn; i++) {
ll pre = 1;
for (int j = i - 2; j >= 0; j--) {
dp[i] = (dp[i] + c[i - 2][j] * pre % mod * (j + 1) % mod * (j + 1) % mod) % mod;
pre = pre * (i - 1) % mod;
}
dp[i] = dp[i] * i % mod;
}
for (int i = 0; i < maxn; i++) {
f[i] = 1;
for (int j = i - 2; j >= 1; j--)f[i] = f[i] * i % mod;
}
res[0] = 1,res[1] = 1;
for (int i = 2; i < maxn; i++) {
for (int j = i; j >= 1; j--) {
res[i] = (res[i] + res[i - j] * c[i - 1][j - 1] % mod * f[j]%mod)%mod;
}
// printf("res[%d]=%lld
",i,res[i]);
}
while(T--){
int n;
scanf("%d",&n);
ll ans = 0;
for(int i = 2;i<=n;i++) {
ans = (ans + c[n][i] * res[n - i] % mod * dp[i] % mod)%mod;
// printf("c[%d][%d]=%lld res[%d]=%lld dp[%d]=%lld
",n,i,c[n][i],n-i,res[n-i],i,dp[i]);
}
printf("%lld
",ans);
}
}