prufer序学习小记+2020牛客暑期多校训练营(第七场)I Valuable Forests

prufer序与无根树的关系

对于一颗带标号无根树,定义其prufer序为按照如下规则生成的序列:
进行n-2步,对于第i步,选择度为1且编号最小的点x,将边的另一端点标号y作为序列的第i位,并删去x和边。
若长度为n-2的序列a[1~n-2]满足:任意a[i]∈[1,n],那么该序列为某棵树对应的prufer序,可按照如下方法根据prufer序构造无根树,且该无根树对应的prufer序为a:
设初始点集V={1…n},先进行n-2步:第i步取a[i],并在V-∪a[i~n-2]中取编号最小的元素x,为a[i]与x连边,并删去V中的x。
最后一步,对V中剩下两节点连边。
样例随便画一棵树

定理:不同的带标号无根树得出的prufer序不同。
推论: f : 树->a[1~n-2] | ∀a[i]∈[1,n] 就是一个单射,又因为a[]必定能对应回原本的树,所以他们之间存在一一映射的关系。

可以证明该推论的一个必要条件成立,帮助理解:
合法序列a[]必定对应一颗树而不是一般图:从上面的构造法可以看出,V中每个点都有连边,且总边数为n-1,只能是一棵树。若不是,必定存在一个点没有任何连边,与构造法矛盾。

性质1:大小为n的带标号无根树有n^(n-2)个

由于prufer序有n^(n-2)种,对应的树也有这么多。

性质2:度数为d的节点在prufer序中出现d-1次

因为一个点只剩一个度数的时候是不会再被计入prufer序内的。

性质3:给定点度数d[1~n]可以算出合法无根树数量

简单计数
c n t = ( n − 2 ) ! ∏ ( d [ i ] − 1 ) ! cnt=\frac{(n-2)!}{\prod (d[i]-1)!} cnt=(d[i]1)!(n2)!
根据这些性质可以解决一些计数问题。


Valuable Forests

我们先求一颗大小为1~n的树的贡献,再组合成森林求贡献。
对于一个大小为n的树,所有合法情况的度数平方和f[n]是多少呢?
考虑每一种prufer序,贡献就是
∑ a [ ] ∑ x = 1... n ( a p p e a r a [ ] [ x ] + 1 ) 2 \sum_{a[]}\sum_{x=1...n} (appear_{a[]}[x]+1)^2 a[]x=1...n(appeara[][x]+1)2
appear_a代表x在prufer序a里出现几次。
我们可以分开统计每个点的贡献,这是经典套路了。具体地,枚举x的度数,计算这种情况下对应的a[]的方案数。又因为每个点是等价的,那么总答案变成
f [ n ] = n ∑ j = 1.. n − 1 j 2 C n − 2 j − 1 ( n − 1 ) n − 2 − ( j − 1 ) f[n]=n\sum_{j=1..n-1}j^2C_{n-2}^{j-1}(n-1)^{n-2-(j-1)} f[n]=nj=1..n1j2Cn2j1(n1)n2(j1)
j枚举了点x的度数。
现在我们得到了f[1~n],怎么计算森林呢?
设大小为n的森林的答案是g[n],要得到他,我们可以枚举n号点形成的树的大小i(如果随便选i个点就会计重),再利用g[1~n]得到答案。
g [ n ] = ∑ i = 1.. n C n − 1 i − 1 ( g [ n − i ] ∗ i i − 2 + c n t [ n − i ] ∗ f [ i ] ) g[n]=\sum_{i=1..n}C_{n-1}^{i-1}(g[n-i]*i^{i-2}+cnt[n-i]*f[i]) g[n]=i=1..nCn1i1(g[ni]ii2+cnt[ni]f[i])
其中cnt[n-i]代表n-i个点形成的森林数。
当我把n-i的森林和i的树拼起来的时候,方案数就变成了cnt[n-i]*i^(i-2),原本森林的贡献和的方案数是cnt[n-i],但现在方案变大了,要乘上放大倍数。反过来树的贡献和也要乘,这样就能得到新方案数下的贡献和了。
c n t [ n ] = ∑ i = 1.. n C n − 1 i − 1 c n t [ n − i ] i i − 2 cnt[n]=\sum_{i=1..n}C_{n-1}^{i-1}cnt[n-i]i^{i-2} cnt[n]=i=1..nCn1i1cnt[ni]ii2

代码

#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
typedef long long ll;
typedef double db;
const int N=5e3+5,M=1e6+5;
ll f[N],cnt[N],g[N],pwv;
int c[N][N],pw[N][N],n,m,i,mo; 
void predo(int n)
{
	int i,j;
	c[0][0]=1;
	pw[0][0]=1;
	fo(i,1,n)
	{
		c[i][0]=1;
		pw[i][0]=1;
		fo(j,1,n)
		{
			c[i][j]=(c[i-1][j-1]+c[i-1][j])%mo;
			pw[i][j]=pw[i][j-1]*(ll)i%mo;
		}
	}
}
void predp(int nn)
{
	int n,i,j;
	fo(n,1,nn)
	{
		fo(j,1,n-1)
			f[n]=(f[n]+1ll*j*j%mo*c[n-2][j-1]%mo*pw[n-1][n-2-(j-1)])%mo;
		f[n]=f[n]*n%mo;
	}
	cnt[0]=1;
	fo(n,1,nn)
	{
		fo(i,1,n)//mind i-2<0
		{
			if (i>1) pwv=pw[i][i-2];else pwv=1;
			cnt[n]=(cnt[n]+(ll)c[n-1][i-1]*cnt[n-i]%mo*pwv)%mo;
		}
	}
	g[0]=0;
	fo(n,1,nn)
	{
		fo(i,1,n)
		{
			if (i>1) pwv=pw[i][i-2];else pwv=1;
			g[n]=(g[n]+((ll)g[n-i]*pwv+(ll)cnt[n-i]*f[i])%mo*c[n-1][i-1])%mo;
		}
	}
	
}
int main()
{
    freopen("I.in","r",stdin);
	//freopen("B.out","w",stdout);
	scanf("%d %d",&m,&mo);
	predo(5e3);
	predp(5e3);
	fo(i,1,m)
	{
		scanf("%d",&n);
		printf("%lld\n",g[n]);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值