USACO 2.3 Cow Pedigrees 【DP+前缀和优化】
题目:
农民约翰准备购买一群新奶牛。 在这个新的奶牛群中, 每一个母亲奶牛都生两个小奶牛。这些奶牛间的关系可以用二叉树来表示。这些二叉树总共有N个节点(3 <= N < 200)。这些二叉树有如下性质:
每一个节点的度是0或2。度是这个节点的孩子的数目。
树的高度等于K(1 < K < 100)。高度是从根到最远的那个叶子所需要经过的结点数; 叶子是指没有孩子的节点。
有多少不同的家谱结构? 如果一个家谱的树结构不同于另一个的, 那么这两个家谱就是不同的。输出可能的家谱树的个数除以9901的余数。
分析:
这是一道动态规划题目,问什么设什么,于是:
我们设f[i][j]表示结构为i层,j个结点的树的个数。
若想要构造它,就必须用它的两个子树去拼它。因为它是i层,所以它必须有至少一个子树的深
度为i-1才能“撑起”它,所以我们分了3种情况:
左子树深度为i-1,右子树深度小于i-1;
左子树深度小于i-1,右子树深度为i-1;
左、右子树深度都为i-1;
事实上,当我们在构造一棵深度为i的树时,我们只关心使用的子树深度是否为i-1或更小。
因此,我们用一个small数组表示深度小于i-1且有j个节点的树的个数。实际操作时我们会
发现,small实际上是一个前缀和数组。
下面是代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define mod 9901 using namespace std; int f[102][202]; int small[102][202]; int num,lev; void DP() { f[1][1]=1; for(int i=2;i<=lev;i++) { for(int j=1;j<=num;j+=2) { for(int k=1;k<=j-k-1;k+=2) { int c=0; if(k==j-k-1) c=1; else c=2; f[i][j]+=c*(f[i-1][k]*f[i-1][j-k-1]); f[i][j]+=c*(f[i-1][k]*small[i-2][j-k-1]); f[i][j]+=c*(f[i-1][j-k-1]*small[i-2][k]); f[i][j]%=mod; } for(int p=0;p<=num;p++) { small[i-1][p]=f[i-1][p]+small[i-2][p]; small[i-1][p]%=mod; } } } } int main() { cin>>num>>lev; DP();//how many dotts and how many levels cout<<f[lev][num]<<endl; return 0; }
或者不要把更新small放到第二层循环中,在求出一个f[i][j] 后,更新small,要注意的是,此时要用“+=”
我推荐这个版本,它的时间复杂度低一些。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define mod 9901 using namespace std; int f[102][202]; int small[102][202]; int num,lev; void DP() { f[1][1]=1; for(int i=2;i<=lev;i++) { for(int j=1;j<=num;j+=2) { for(int k=1;k<=j-k-1;k+=2) { int c=0; if(k==j-k-1) c=1; else c=2; f[i][j]+=c*(f[i-1][k]*f[i-1][j-k-1]); f[i][j]+=c*(f[i-1][k]*small[i-2][j-k-1]); f[i][j]+=c*(f[i-1][j-k-1]*small[i-2][k]); f[i][j]%=mod; } } for(int p=0;p<=num;p++) { small[i-1][p]+=f[i-1][p]+small[i-2][p]; small[i-1][p]%=mod; } } } int main() { cin>>num>>lev; DP();//how many dotts and how many levels cout<<f[lev][num]<<endl; return 0; }