LOJ #6495. 「雅礼集训 2018 Day1」树

今天我们又做师兄出的比赛了!
我一道题都不会!
抱灵了!
要是这是CSP - Sday1难度我就赶紧退役滚回去搞课内吧
第一题就是期望题……

题外话:师兄出题时把输出的第一行去掉了,就是说我们在考试的时候并不需要输出四舍五入的结果
别看它看上去很像概率期望DP,实际上你会发现你根本没有办法直接对期望进行状态转移
这道题实质上是一个计数类DP面向数据题
我们先统计出各种树的数量再给它们做一个平均值就好了

既然是DP我们就来看一看它是怎么转移的。
f i , j f_{i,j} fi,j为i个节点,深度为j的树的种数
容易得到 f 1 , 1 = f 2 , 2 = 1 f_{1,1}=f_{2,2}=1 f1,1=f2,2=1
观察题面: i i i号节点的父亲在 [ 1 , i − 1 ] [1,i-1] [1,i1]内随机
那就是说 2 2 2的父亲一定是 1 1 1啊!

既然如此我们就从这两个点的固定关系入手。
后面来的点一定会以两者之一为根节点
所以我们将整棵树分为两个部分:以2为根的子树和其它部分
由for循环i枚举树的大小,j枚举树的高度
分类讨论:

  1. 2 2 2为根的子树中最深节点的深度比其它部分的要小
    首先,这个其它部分一定要存在,至少有一个节点,再减去 1 1 1这个节点,以 2 2 2为根的子树(太麻烦了,直接称作树 A A A,其它部分称作树 B B B)大小 k k k一定小于等于 i − 2 i-2 i2
    而为了保持它的最深子节点深度比树B的深度小,深度 l l l要小于等于 j − 2 j-2 j2
    这样,这棵树的结构数就有两边乘起来这么多。
    而两边的结构数已经算好了。
    还有一点:我们要枚举两棵子树中的节点各是哪些,所以要乘上一个组合数
    *上一层的组合数仅仅只枚举各子树中的结构,不影响当前层
  2. 以2为根的子树中最深节点的深度比其它部分的要大或者两者相等
    这时,树 A A A的最大大小便是 i − 1 i-1 i1了,而且它的最深子节点深度一定是 j − 1 j-1 j1
    所以我们枚举树 B B B的深度,再进行一个枚举

枚举完之后就对每一种有 n n n个节点的形态进行枚举累加,在除以树的总形态数( ( i − 1 ) ! (i-1)! (i1)!),即可得出结果

代码如下

#include
#include
using namespace std;
int C[81][81];
double dC[81][81];
int f[81][81];
double d[81][81];
inline int min(const int a,const int b)
{return a<b?a:b;}
inline int fac(const int x,const int p)
{return x?1ll*x*fac(x-1,p)%p:1;}
inline double fac(const int x)
{return x?x*fac(x-1):1;}
inline int pow(int a,int b,const int p)
{
	int ans=1;
	for(;b;b>>=1,a=1ll*a*a%p)
		if(b&1)
			ans=1ll*ans*a%p;
	return ans;
}
int main()
{
	int n,p;scanf("%d%d",&n,&p);
	for(int i=0;i<=n;i++)
	{
		C[i][0]=1;
		dC[i][0]=1;
		for(int j=1;j<=i;j++)//递推求组合数,杨辉三角
		{
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%p;
			dC[i][j]=dC[i-1][j]+dC[i-1][j-1];
		}
	}
	d[1][1]=d[2][2]=f[1][1]=f[2][2]=1;
	for(int i=3;i<=n;i++)//i个点
		for(int j=2;j<=i;j++)//深度为j
		{
			//先枚举以2为根的子树深度小于以一为根子树的情况
			for(int k=1;k<=i-2;k++)//以2为根的子树节点数
				for(int l=1;l<=min(j-2,k);l++)//深度为l
				{
					f[i][j]=(f[i][j]+1ll*f[k][l]*f[i-k][j]%p*C[i-2][k-1]%p)%p;
					d[i][j]+=            d[k][l]*d[i-k][j] *dC[i-2][k-1];
				}
				//枚举哪些点在该子树中
			//以1为根的树深度小于等于树B的情况
			for(int k=1;k<i;k++)//以2为根的子树节点数
				for(int l=1;l<=j;l++)//以1为根的子树深度
				{
					f[i][j]=(f[i][j]+1ll*f[k][j-1]*f[i-k][l]%p*C[i-2][k-1]%p)%p;
					d[i][j]+=            d[k][j-1]*d[i-k][l] *dC[i-2][k-1];
				}
		}
	int ans=0;
	double dans=0;
	for(int i=1;i<=n;i++)
	{
		ans=(ans+1ll*i*f[n][i]%p)%p;
		dans+=i*d[n][i];
	}
	printf("%.0lf\n",round(dans/fac(n-1)));
	printf("%lld",1ll*ans*pow(fac(n-1,p),p-2,p)%p);
	return 0;
}

你可能感兴趣的:(做题笔记)