Valuable Forests

题目链接

题目大意

定义一个树的权值为它的所有顶点度数的平方和。
森林的权值为所有树的权值和。
求有n个点,带编号点的,所有的森林的权值和。
带编号意思是1-2-3 不等于 1-3-2

题解

prufer序列学习博客
大佬题解博客
看的这两篇博客学会的这道题
(其实还是有点懵逼)
可以看上面的博客,我写博客完全为了加深印象。 。。
先预处理几个数组:

定义N[i] 代表具有i个点的树有N[i]种
根据pruper序列中的结论:一颗有n个节点的树有n的n - 2次方中形态,(因为pruper序列中每个数都可以是n个数中的任意一个)。

N[1] = 1;
for (int i = 2; i <= M; i ++ )
{
   
	N[i] = qpow(i,i - 2,mod);
}

定义num[i]代表具有i个点的森林有num[i]种
怎么算num[i] ?
枚举有 j 个点与第i个点合并成一棵树。
状态转移: 从除了 i 这个点其他的 i - 1个点中选 j 个跟 i 组成一颗树。这棵树的大小是 j + 1 这棵树的有 A[j + 1] 种,还剩 i - j - 1 个点。 i - j - 1 个点组成的森林有num[i - j - 1] 种.
所以状态转移就是

num[0] = 1;
for (int i = 1; i <= M; i ++ )
{
   
	for (int j = 0; j < i; j ++ )
	{
   
		num[i] = (num[i] + C[i - 1][j] * num[i - j - 1] % mod * N[j + 1] % mod) % mod;
	}
}

定义A[i]表示 i 个节点所有的树的权值
可以枚举一个点的度数,然后 d ^ 2 乘这个点度数是 d 的树的种类数。
这个点度数是 d 的时候,prufer序列有d - 1个是这个点其他是另外 i - 1 个点。
度数为d的时候的贡献是 d * d * C(i - 2, d - 1) * (i - 1) ^ (i - 2 - d + 1);
一个点的贡献是这么多,i 个点的再乘 i 就可以了

for (ll i = 2; i <= M; i ++ )
{
   
	ll s= 0;
	for(ll d = 1; d < i; d ++ )
	{
   
		s = (s + d * d % mod * C[i - 2][d - 1]% mod * qpow(i - 1,i - 2 - d + 1, mod) % mod) % mod;
	}
	A[i] = s * i % mod;
}

什么?有重复???有没有考虑过这个问题?当然。
他没有重复,为什么呢?因为算的是度数的贡献,如果单纯的算种类数的话是有重复的,算度数的贡献是种类数乘当前这个点度数是d的时候。因为只算这个点的贡献,其他点的贡献没有算进去,所以不重复。

然后就是答案数组了:
定义ans[i] 代表有i个点的森林的权值总和
可以枚举 j 个点与第 i 个点相邻形成一颗树。
那么答案就是i - j - 1个点的贡献 + 形成的当前这颗树的贡献。
i - j - 1个点形成的森林对当前答案的贡献是:j + 1个点形成的树的种类数乘ans[i - j - 1]。j + 1个点形成的种类数是C(i - 1,j) * N[j + 1].
当前这颗树的贡献是:i - j - 1形成的森林数量 * 这颗树的贡献。

for (int  i= 1; i <= M; i ++ )
{
   
	for (int j = 0; j < i; j ++ )
	{
   
		ans[i] = (ans[i] + C[i - 1][j] * N[j + 1] % mod * ans[i - j - 1] % mod + C[i - 1][j] * A[j + 1] % mod * num[i - j - 1] % mod) % mod;
	}
}

代码:

#include<algorithm>
#include<iostream>
#include <cstdio>
#include <string>
#include <queue>
#include <cstring>
#include <stack>
#include <set>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<double,double> pdd;
typedef unsigned long long ull;
typedef set<int>::iterator sit;
#define st first
#define sd second
#define mkp make_pair
#define pb push_back
void tempwj(){
   freopen("hash.in","r",stdin);freopen("hash.out","w",stdout);}
ll gcd(ll a,ll b){
   return b == 0 ? a : gcd(b,a % b);}
ll qpow(ll a,ll b,ll mod){
   a %= mod;ll ans = 1;while(b > 0){
   if(b & 1)ans = ans * a % mod;a = a * a % mod;b >>= 1;}return ans;}
struct cmp{
   bool operator()(const pii & a, const pii & b){
   return a.second < b.second;}};
int lb(int x){
   return  x & -x;}
//friend bool operator < (Node a,Node b) 重载
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
// const ll mod = 1e9+7;
const int maxn = 5e3+10;
const int M = 5e3;
ll A[maxn];
ll num[maxn];
ll ans[maxn];
ll N[maxn];
ll mod;
ll C[maxn][maxn];
void init()
{
   
	C[0][0] = 1;
	for (int i = 1; i <= M; i ++ )
	{
   
		C[i][0] = 1;
		// C[i][1] = i;
		for (int j = 1; j <= i; j ++ )
		{
   
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;;
		}
	}
	N[0] = 1;
	N[1] = 1;
	for (int i = 2; i <= M; i ++ )
	{
   
		N[i] = qpow(i,i - 2,mod);
	}
	num[0] = 1;
	for (int i = 1; i <= M; i ++ )
	{
   
		for (int j = 0; j < i; j ++ )
		{
   
			num[i] = (num[i] + C[i - 1][j] * num[i - j - 1] % mod * N[j + 1] % mod) % mod;
		}
	}
	// for (int i = 1; i <= 4; i ++ )
	// printf("%lld ",num[i]);
	for (ll i = 2; i <= M; i ++ )
	{
   
		ll s= 0;
		for(ll d = 1; d < i; d ++ )
		{
   
			s = (s + d * d % mod * C[i - 2][d - 1]% mod * qpow(i - 1,i - 2 - d + 1, mod) % mod) % mod;
		}
		A[i] = s * i % mod;
	}
	// for (int i= 2; i<= 4; i ++ )
	// printf("%lld\n",A[i]);
	for (int  i= 1; i <= M; i ++ )
	{
   
		for (int j = 0; j < i; j ++ )
		{
   
			ans[i] = (ans[i] + C[i - 1][j] * N[j + 1] % mod * ans[i - j - 1] % mod + C[i - 1][j] * A[j + 1] % mod * num[i - j - 1] % mod) % mod;
		}
	}
}
int main()
{
   
	int T;
	scanf("%d%lld",&T,&mod);
	init();
	while(T -- )
	{
   
		int x;
		scanf("%d",&x);
		printf("%lld\n",ans[x]);
	}
}