2020牛客暑期多校训练营(第七场)——I Valuable Forests

2020牛客暑期多校训练营(第七场)——I  Valuable Forests

2020牛客暑期多校训练营(第七场)——I Valuable Forests_第1张图片
样例输入

5 1000000007
2
3
4
5
107


样例输出

2
24
264
3240
736935633

题目大意
2020牛客暑期多校训练营(第七场)——I Valuable Forests_第2张图片
 

题解

不建议看官方题解(反正你也看不懂,否则为什么要来看博客)因为难度较大。

本题核心在于推公式。

 

因为n个点的无根树可以形成n^{n-2}个不同的树,我们设他的值为st_n,设n个点的森林个数为f_n。在第n个点加入时,我们选择i个点和他形成一棵树,那么就可以列出dp式:

st_n=\sum_{i=0}^{n-1}C_{n-1}^if(n-i-1)st_{i+1}=n^{n-2}

代码实现

for(int i=2;i<=5000;i++)
{
	st[i]=quick_pow(i,i-2);
}//quick_pow快速幂

接着我们再定义a_n​为n个点能形成的所有无根树的价值和。枚举每一个点i,再枚举这个点的度数j。第i个点的度数为j的贡献为j^2与序列中有且仅有j-1i​​​​​​​的方案数之积。a_n​的dp式为:

a_n=\sum_{j=1}^{n-1}C_{n-2}^{j-1} j^2(n-1)^{n-j-1}

代码实现

for(int i=1;i<=5000;i++)
{
	for(int j=1;j<=i-1;j++)
	{
	    a[i]=(1ll*j*j*c[j-1][i-2]%mod*quick_pow(i-1,i-2-j+1)+a[i])%mod; 
	}
	a[i]=1ll*i*a[i]%mod;
}

 

最后,设n个点的形成的森林的价值和为ans_n​。
我们每次加入第n个点,再选择i个点,和它形成一棵树。选择i个点和第n个点,形成一个有i+1个点的树与之相对应,其他的点能形成ans_{n-i-1}中的森林,对于每一种形成方式,都能得到a_{i+1}的权值贡献,乘在最后。这样,我们得到最后的式子:

ans_n=\sum_{i=0}^{n-1}C_{N-1}^i(st_{i+1}ans_{n-i-1}+f_{n-i-1}a_{i+1})

代码实现

for(int i=2;i<=5000;i++)
{
	for(int j=1;j<=i;j++)
    {
		ans[i]=(1ll*c[j-1][i-1]*((1ll*st[j]*ans[i-j]%mod+1ll*f[i-j]*a[j]%mod)%mod)+ans[i])%mod;
    }
}

AC Code

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include
using namespace std;
int n,c[5010][5010],a[5010],ans[5010],f[5010],st[5010],T,mod;
int quick_pow(int x,int a)
{
	int ans=1;
	while(a)
	{
		if(a&1)ans=(1ll*ans*x)%mod;
		x=(1ll*x*x)%mod;a>>=1;
	}
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	c[0][0]=1;st[0]=st[1]=1;
	cin>>T>>mod;
	for(int i=1;i<=5000;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<=5000;i++)
	{
		for(int j=1;j<=i-1;j++)
		{
			a[i]=(1ll*j*j*c[j-1][i-2]%mod*quick_pow(i-1,i-2-j+1)+a[i])%mod; 
		}
		a[i]=1ll*i*a[i]%mod;
		if(i>1) 
			st[i]=quick_pow(i,i-2);
	}
	f[0]=1;f[1]=1;
	for(int i=2;i<=5000;i++)
		for(int j=0;j>n;
		cout<

 

你可能感兴趣的:(2020牛客暑期多校训练营(第七场)——I Valuable Forests)