2020暑期牛客多校训练营第七场(I)Valuable Forests(dp,组合数学,prufer)

Valuable Forests

原题请看这里

题目描述:

我们将无根树T的权值定义为 u V ( T ) ( d ( u ) ) 2 \sum_{u\in V(T)}(d(u))^2 ,其中 V ( T ) V(T) T T 的所有顶点的集合,而 d ( u ) d(u) 是顶点 u u 的度。 我们将森林的价值定义为森林中所有树木的价值之和。 现在,我们希望您用 N N 个标记的顶点来回答所有森林的值之和。 为了避免计算巨大的整数,请以模 M M 为单位报告答案。

输入描述:

有多个测试用例。 输入的第一行包含两个整数 T T M ( 1 T 5000 1 M 2 30 M M(1 \le T \le 5000,1 \le M \le 2 ^ {30},M 是质数 ) ) ,指示测试用例的数量和模数。对于每个测试用例,唯一的行仅包含整数 N ( 1 N 5000 ) N(1 \le N \le 5000)

输出描述:

对于每个测试用例,输出答案模 M M 的值。

样例输入:

5 1000000007
2
3
4
5
107

样例输出:

2
24
264
3240
736935633

思路:

题意:我们定义一棵无根树的权值是所有点的度数的平方和,求有标号的n个点的所有森林的权值的和。
首先,我们要知道 p r u f e r prufer 序列这个东西: p r u f e r prufer 序列详解 ,由 p r u f e r prufer 序列的结论可以知道:对于一棵有 n n 个节点的无根树,可以形成 n n 2 n^{n-2} 棵不同的树。
现在计这个值为 s u m sum ,我们就可以求出对于 n n 个节点的森林个数 f n f_n
f ( n ) = i = 0 n 1 C n 1 i f ( n i 1 ) s u m ( i + 1 ) f(n)=\mathop{\sum}\limits_{i=0}^{n-1}C^i_{n-1}f(n-i-1)*sum(i+1)
随后我们就可以推出 n n 个点可以形成所有的无根树的权值和 d p n dp_n
d p ( n ) = i = 1 n j = 1 n 1 j 2 C n 2 j 1 ( n 1 ) n j 1 dp(n)=\mathop{\sum}\limits_{i=1}^{n}\mathop{\sum}\limits_{j=1}^{n-1}j^2C^{j-1}_{n-2}*(n-1)^{n-j-1}
根据 p r u f e r prufer 序列的性质,如果节点 i i 的度数为 j j ,那么他的贡献可以看成 j 2 j^2 与序列中有且仅有 j 1 j-1 i i 的方案数之积。
这时我们就发现这个 i i 没什么用…于是可以化简:
d p ( n ) = n j = 1 n 1 j 2 C n 2 j 1 ( n 1 ) n j 1 dp(n)=n\mathop{\sum}\limits_{j=1}^{n-1}j^2C^{j-1}_{n-2}*(n-1)^{n-j-1}
最后,我们就可以求出 n n 个点可以形成的森林的权值和 A n s n Ans_n
A n s ( n ) = i = 0 n 1 C n 1 i ( s u m ( i + 1 ) A n s ( n i 1 ) + f ( n i 1 ) d p ( i + 1 ) ) Ans(n)=\mathop{\sum}\limits_{i=0}^{n-1}C^i_{n-1}*(sum(i+1)*Ans(n-i-1)+f(n-i-1)*dp(i+1))
这样我们就预处理出了所有的 A n s Ans ,再 O ( 1 ) O(1) 查询一下就可以啦

A C AC C o d e Code :

#include <bits/stdc++.h>
using namespace std;
const int N = 5000;
int t, n, mod, C[N + 5][N + 5], dp[N + 5], sum[N + 5], f[N + 5], ans[N + 5];
int ksm(int x, int y, int ret)
{
	while (y)
	{
		if(y & 1) ret = ret * 1ll * x % mod;
		x = ( 1ll * x * x ) % mod;
		y >>= 1;
	}
	return ret % mod;
}//快速幂
int main()
{
	scanf("%d%d", &t, &mod);
	C[0][0] = sum[0] = sum[1] = f[0] = f[1] = 1;
	for (int i = 1; i <= N; ++i)
	{
		C[0][i] = 1;
		for (int j = 1; j <= i; ++j)
			C[j][i] = ( 1ll * C[j][i-1] + C[j-1][i-1] ) % mod;
	}//杨辉三角组合数打表
	for (int i = 1; i <= N; ++i)
	{
		for (int j = 1; j < i; ++j)
			dp[i] = ( ( ( 1ll * j * j *C[j-1][i-2] ) % mod * ksm( i-1 ,i-j-1 ,1 ) ) % mod + dp[i] ) % mod;
		dp[i] = ( 1ll * i * dp[i] ) % mod;
		if(i ^ 1) sum[i] = ksm( i, i-2, 1);
	}
	for (int i = 2; i <= N; ++i)
		for (int j = 0; j < i; ++j)
			f[i] = ( ( 1ll * C[j][i-1] * f[i-j-1] ) % mod * sum[j+1] + f[i] ) % mod;
	for (int i = 2; i <= N; ++i)
		for (int j = 1; j <= i; ++j)
			ans[i] = ( 1ll * C[j-1][i-1] * ( ( 1ll * sum[j] * ans[i-j] ) % mod + 1ll * f[i-j] * dp[j] % mod ) % mod + ans[i] ) % mod;
	while (t--)
	{
		scanf("%d", &n);
		printf("%d\n", ans[n] % mod);
	}
}

猜你喜欢

转载自blog.csdn.net/s260127ljy/article/details/107743757