计蒜客 T3225 Darko 的生成树

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

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

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

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

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

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

\(g_{i,j}=\sum\limits_{k<l<j}[k+l==i-1]f_k\times f_l\),那么上面第一类就可以写成这样:\(f_i+=\sum\limits_{j=0}^{i-1} f_j\times 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\n",f[n]);
}

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

posted @ 2020-08-24 00:13  With_penguin  阅读(148)  评论(0编辑  收藏  举报