《训练指南》中的第二种算法,其实本质上就是个背包。d[i][j]表示,在子树的节点数最大为i的情况下,j个节点的解。当之前的i-1,i-2,....0的结果都已知的时候,d[i][j]自然可根据下式求解:
d[i][j]=sum{C(f(i)+p-1,p)*d[i-1][j-p*i] | p*i<=j}
其中f(i)表示恰好有i个节点的子树的数量。而C(f(i)+p-1,p)则表示有p棵i节点子树形成的组合数。然后运用乘法原理,剩余的节点数为j-p*i且包含的子树最多只有i-1个节点。继而从i=2时开始遍历,将所有的d[i][j]求出,而f(i)=d[i-1][i]。最后的解f(n)自然等于d[n-1][n]。
还要注意的一点是边界的问题。当d[i][j]中的j=0时,则d[i][0]=1,这与求解背包时的边界相似。d[0][1]=1,表示叶子节点。而其他的d[0][i]则都为0,因为有i(i>1)个节点,但没有子树的最大节点数为0,显然是不可能的。最后,在输出结果时,f(1)始终为1,但其他的f(i)要乘以2,因为正如书中所说,根节点为并联节点或串联节点将决定两个不同的序列。
注:关于组合数的求解为什么能这样做,希望有高手能解答!
#include <iostream> #include <cstdio> #define MAX 30+2 using namespace std; long long d[MAX][MAX]; long long f[MAX]; long long C(long long n,long long m)//求解组合数C(n,m) { double ans=1; for(int i=0;i<m;++i) ans*=n-i; for(int i=1;i<=m;++i) ans/=i; return (long long)(ans+0.5); } int main() { //freopen("data.txt","r",stdin); for(int i=0;i<MAX;++i) d[i][0]=d[1][i]=1;//注意边界 for(int i=2;i<MAX;++i) d[0][i]=0; d[0][1]=1; for(int i=2;i<MAX;++i){ f[i]=d[i-1][i]; for(int j=1;j<i;++j) d[i][j]=d[i-1][j];//最大子树的节点数大于总节点数,则继承 for(int j=i;j<MAX;++j) for(int p=0;p*i<=j;++p) d[i][j]+=C(f[i]+p-1,p)*d[i-1][j-p*i]; } f[1]=1; for(int i=2;i<MAX;++i) f[i]*=2;//修改f(i) int N; while(cin>>N&&N){ cout<<f[N]<<endl; } return 0; }
时间: 2024-10-05 09:53:40