计蒜客 T3225 Darko 的生成树

之前没做过无标号有根树有关的题目,考场上写个错的DP过了 (30pts)...

首先根据题目的条件,我们在脑海中大概能想象出这棵树的样子。假设初始状态只有两个相连的点,其中一个是根,另一个是叶子。定义一次生长为:在一个叶子下面接上三个新点。发现符合条件的树只能由初始状态生长若干次得到。题目就转化为了 (n) 次生长后形成的无标号有根树计数。

考虑树形DP:设 (f_i) 表示经过 (i) 次生长后本质不同的树的数量。(f_i) 可以分三类统计(因为一个节点下面只能挂三个叶子,所以自然的想到枚举每个子树生长多少次。另,下面的 (i-1) 中减一的原因是这个点本身要生长一次):

  1. 下面挂的三个叶子生长次数两两不同。这时直接保证枚举的生长次数 (j<k<l) 即可保证没有重复,即 (f_i+=sumlimits_{j<k<l}[j+k+l=i-1]f_j imes f_k imes f_l)
  2. 下面挂的三个叶子其中两个生长次数是相同的。思考方向就是要保证不重不漏:(f_i+=sumlimits_{2j+k=i-1}[j eq k]f_k imes (f_j+inom {f_j} 2))
  3. 下面挂的三个叶子的生长次数都相同(首先要保证 (i-1equiv 0 pmod 3)):(f_i+=inom {f_j} 3 +2inom {f_j} 2+f_j)(j=frac{i-1}3))。

时间复杂度 (O(n^3)),可以通过此题(但是计蒜客上直接能草过去emmm)。

但按照道理说 (O(n^3)) 是不行的qwq,所以我们有一个小小的trick。

(g_{i,j}=sumlimits_{k<l<j}[k+l==i-1]f_k imes f_l),那么上面第一类就可以写成这样:(f_i+=sumlimits_{j=0}^{i-1} f_j imes g_{i-j,j})。然后 (g,f) 互相转化就可以做到 (O(n^2)) 的复杂度了。

代码(因为懒得优化直接写了个 (O(n^3)) 的):

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

typedef long long LL;
const int N=1009,M=998244353;
int n;
LL f[N],inv2,inv6;

int ksm(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1)
			res=1ll*res*a%M;
		b>>=1,a=1ll*a*a%M;
	}
	return res;
}

void init()
{
	scanf("%d",&n);
	inv2=ksm(2,M-2),inv6=ksm(6,M-2);
}

void work()
{
	f[0]=f[1]=1;
	for (int i=2;i<=n;i++)
	{
		for (int j=0;j<=i-1;j++)
			for (int k=j+1;k<=i-1-j;k++)
			{
				int l=i-1-j-k;
				if(l<=k) continue;
				f[i]=(f[i]+f[j]*f[k]%M*f[l]%M)%M;
			}
		for (int j=0;j<=i-1;j++)
		{
			int k=i-1-2*j;
			if(k<0||k==j) continue;
			f[i]=(f[i]+f[k]*((f[j]*(f[j]-1)%M*inv2%M+f[j])%M)%M)%M;
		}
		if((i-1)%3==0)
		{
			LL j=f[(i-1)/3];
			f[i]=(f[i]+j*(j+1)%M*(j+2)%M*inv6%M)%M;
		}
	}
	printf("%lld
",f[n]);
}

int main()
{
	init();
	work();
	return 0;
}

原文地址:https://www.cnblogs.com/With-penguin/p/13551538.html