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

Valuable Forests

原题请看这里

题目描述:

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

输入描述:

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

输出描述:

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

样例输入:

5 1000000007
2
3
4
5
107

样例输出:

2
24
264
3240
736935633

思路:

题意:我们定义一棵无根树的权值是所有点的度数的平方和,求有标号的n个点的所有森林的权值的和。
首先,我们要知道 p r u f e r prufer prufer序列这个东西: p r u f e r prufer prufer序列详解 ,由 p r u f e r prufer prufer序列的结论可以知道:对于一棵有 n n n个节点的无根树,可以形成 n n − 2 n^{n-2} nn2棵不同的树。
现在计这个值为 s u m sum sum,我们就可以求出对于 n n n个节点的森林个数 f n f_n fn
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) f(n)=i=0n1Cn1if(ni1)sum(i+1)
随后我们就可以推出 n n n个点可以形成所有的无根树的权值和 d p n dp_n dpn
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} dp(n)=i=1nj=1n1j2Cn2j1(n1)nj1
根据 p r u f e r prufer prufer序列的性质,如果节点 i i i的度数为 j j j,那么他的贡献可以看成 j 2 j^2 j2与序列中有且仅有 j − 1 j-1 j1 i i 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} dp(n)=nj=1n1j2Cn2j1(n1)nj1
最后,我们就可以求出 n n n个点可以形成的森林的权值和 A n s n Ans_n Ansn
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)) Ans(n)=i=0n1Cn1i(sum(i+1)Ans(ni1)+f(ni1)dp(i+1))
这样我们就预处理出了所有的 A n s Ans Ans,再 O ( 1 ) O(1) O(1)查询一下就可以啦

A C AC AC C o d e Code Code:

#include 
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);
	}
}

你可能感兴趣的:(动态规划)