[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\n",f[2*i-1][0]);
	return 0;
}

总结

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

posted @ 2019-09-17 17:07  jz_597  阅读(184)  评论(0编辑  收藏  举报