题目大意
所谓的优美的树需要满足如下条件:
1. 这是一棵有根二叉树;
2. 非叶节点需有两个儿子;
3. 不可以变换为k-左偏树。
所谓的k-左偏树是指一棵有k 个叶子的树,每个非叶节点的右儿子均为叶子且均有左儿子。
所谓的变换指的是经过若干次如下两种变换:
1. 删去一个节点的两个儿子;
2. 用一个节点的某个儿子替换该节点。
现在给你k 和n,想要你求出叶子数为1,2,3…n 的优美的树分别
有多少。
分析
我们手动画几棵树,就可以发现,如果可以变换成k-左偏树,那么从根某个叶子的路径要走k次左边。
为了好看,我们把叶子去掉,那么就变成了k-1-左偏链。
先设个方程吧,f[i][j]为放了i个叶子,最多走左边j次?不行,不知道往哪里加,万一较小的加多了就不好了。而且乱加一通会重复的。
那我们想想办法解决。
为了保证不乱加,我们规定按先序遍历加点。
这样的话,我们往下加一个点,要么就是上一次新加点的左右儿子,要么是其他点的右儿子,具体是哪些点呢?等下再说。
我们保证的是左偏次数任何时候不超过k-1,那么记录最多走j次左边会不会没有用?
我们观察到,新的节点i向左走的次数最多是i-1的+1,而且放了之后,i-1就没什么用了,因为我们不能在上面放左儿子了。那么只要我们保证新放的点左走次数不超过k-1,那无论如何,之前的点都不可能超过k-1,因为除了上一次放的点,其他点不会增加了,而若新放的点不超,它的父亲肯定不超嘛。只要不超k-1,靠着i这一信息,我们可以给答案准确贡献。那我们把上面那个j换成,当前i这个节点的左偏次数为j,就很舒服了,每个f[i][j]里的方案左偏次数都不会超过k-1。
好了,我们可以考虑转移了,新加入的点要么是上一次的左儿子,要么是某个点的右儿子,是哪些点呢?我们观察到,任取左偏次数一样的点,其中只有一个能放一个右儿子
,左儿子因为先序遍历的关系不能放。为什么呢?若给左偏次数l的点放一个右儿子,那么这个点不能再放了,而新的点左偏次数仍是i,等于继承了位置,所以只有一个合法位置。
那么明显,f[i][j]可以转移到f[i][1]….f[i][j+1],这个明显可以用前缀和优化转移,那么时间复杂度O(n2)O(n2)
总结:这道题最重要的是发现结论,设出最好的状态表示一棵树,转移的优化倒是其次。为了简化做法,我们要对一些东西进行规定。
套路:先序遍历,全局限制代替局部状态记录的限制作用。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=5005,kk=105;
const int mo=1000000009;
int K,n,i,j,k,f[N][N],tot;
int main()
{
freopen("shu.in","r",stdin);
freopen("shu.out","w",stdout);
scanf("%d %d",&K,&n);
f[0][1]=1;
f[1][1]=1;
fo(i,1,n)
{
if (i!=1)
fo(j,1,K-1)
(f[i][j]+=f[i-1][j-1])%=mo;
fd(j,K-1,1) (f[i][j]+=f[i][j+1])%=mo;
}
fo(i,1,n)
{
// printf("%d\n",f[i][1]);
}
printf("%d\n",!(1));
return 0;
}