2020牛客暑期多校训练营(第七场)——I Valuable Forests
![](https://i-blog.csdnimg.cn/blog_migrate/c98db7f2a641822946952f5f00b5b2bc.png)
样例输入
5 1000000007
2
3
4
5
107
样例输出
2
24
264
3240
736935633
题目大意
题解
不建议看官方题解(反正你也看不懂,否则为什么要来看博客)因为难度较大。
本题核心在于推公式。
因为个点的无根树可以形成
个不同的树,我们设他的值为
,设n个点的森林个数为
。在第
个点加入时,我们选择
个点和他形成一棵树,那么就可以列出dp式:
代码实现
for(int i=2;i<=5000;i++)
{
st[i]=quick_pow(i,i-2);
}//quick_pow快速幂
接着我们再定义为
个点能形成的所有无根树的价值和。枚举每一个点
,再枚举这个点的度数
。第
个点的度数为j的贡献为
与序列中有且仅有
个
的方案数之积。
的dp式为:
代码实现
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个点的形成的森林的价值和为。
我们每次加入第个点,再选择
个点,和它形成一棵树。选择
个点和第
个点,形成一个有
个点的树与之相对应,其他的点能形成
中的森林,对于每一种形成方式,都能得到
的权值贡献,乘在最后。这样,我们得到最后的式子:
代码实现
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<bits/stdc++.h>
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<i;j++)
f[i]=(1ll*c[j][i-1]*f[i-j-1]%mod*st[j+1]+f[i])%mod;
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;
while(T--)
{
cin>>n;
cout<<ans[n]<<endl;
}
}