[JZOJ6353] 【NOIP2019模拟】给

题目

题目大意

对于所有的整数(k in [1,n]),求叶子结点有(k)个的二叉树个数,满足每个非叶子结点都有两个儿子,并且对于每个叶子结点,从根节点到它经过的向左的边数少于等于(m)个。


思考历程

很容易推出这样的(DP)
(f_{i,j})表示(m=i)(n=j)的答案是多少。
(f_{i,j}=sum_{k in [1,n)}{f_{i-1,k}f_{i,j-k}})
这样当然过不了。
然而,如果只看第二维,就会感觉它和卡特兰数长得很像。
于是就往打表的方面想……
打出一个表,表可以分成上下两个三角形,下面的那个三角形就是标准的卡特兰数……
然而找不到上面的规律。
%%%GMH大佬居然找到了。
于是就暴力了……


正解

换一种思路(DP)
(f_{i,j})表示已经放了(i)个节点,从根往下走已经向左走了(j)次。
有两种转移:
一个是继续向左走,那就是转移到(f_{i+1,j+1})
另一个是回到第一个左转的地方,放一个右儿子,那就是转移到(f_{i+1,j-1})
很显然放的节点个数为(2k-1),所以这个方法是能过的。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 5010
#define mo 998244353
#define ll long long
int m,n;
ll f[N*2][N];
int main(){
	freopen("ca.in","r",stdin);
	freopen("ca.out","w",stdout);
	scanf("%d%d",&m,&n);
	f[1][0]=1;
	for (int i=1;i<2*n-1;++i)
		for (int j=0;j<m;++j)
			if (f[i][j]){
				(f[i+1][j+1]+=f[i][j])%=mo;
				if (j)
					(f[i+1][j-1]+=f[i][j])%=mo;
			}
	for (int i=1;i<=n;++i)
		printf("%lld
",f[2*i-1][0]);
	return 0;
}

总结

在做树一类的(DP)的时候,不要仅仅是想着从下往上转移,还要考虑一下按照(dfs)序来转移。

原文地址:https://www.cnblogs.com/jz-597/p/11535406.html