UVa 10253 Series-Parallel Networks

      《训练指南》中的第二种算法,其实本质上就是个背包。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;
}


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。