递推关系的运用加简单DP【UVA11137Ingenuous Cubrency】-------2015年1月27日

一:题意描述

本题就是求立方数之和。输入正整数n,求将n写成若干个正整数的立方和有多少种方法?

二:问题分析

本题主要的难点就是确定状态。我们可以建立多段图。节点(i,j)表示“使用不超过i的整数的立方,累加和为j“这个状态。设d(i,j)表示为从(0,0)到(i,j)的路径条数,最终答案

是d(21,n)。对于这个图而言,每一个节点(i,j)都只能从当前节点(i,j)到达节点(i+1,a*(j+1)^3+j)(a为j+a*(j+1)^3<maxn的常数)所以我们可以写出如下代码:

#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define MAXI 21
#define  MAXN 10000+5
long long d[MAXI+1][MAXN];
int main()
{
    int n;
    memset(d,0,sizeof(d));
    d[0][0]=1;
    for(int i=1;i<=MAXI;i++)
    {
        for(int j=0;j<=MAXN;j++)
        {
            for(int a=0;j+a*i*i*i<=MAXN;a++)
                d[i][j+a*i*i*i]+=d[i-1][j];
        }
    }
    while(cin>>n)
    {
        cout<<d[21][n]<<endl;
    }
}

但是我们可以分析得出这个代码的时间复杂度接近O(N^3).所以我们应该把这个问题加以改进:

对于第i个数字而言,我们可以这样分析:如果我们取一次第i个数字得到d(i,j)那么可以知道这是从状态d(i,j-i^3)转化得到;当我们不取第i个数字得到d(i,j)那么可以知道这是从状态d(i-1,j)转化得到。那么我们便可以得到递推公式:d(i,j)=d(i-1,j)+d(i,j-i^3)(如果大家想说我们如果需要用多个i^3怎么办?是不是这个公式不在成立呢?仔细想想还是成立的,因为我们如果再往下递推一层或者多层我们便可以使用多次i^3,那么我们便可以把代码写成如下形式:时间复杂度仅仅是O(n^2),而且我们可以用滚动数组节约存储空间。

下面给出代码:

#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define  MAXN 22*22*22
long long dp[MAXN];
int main()
{
    int n;
    memset(dp,0,sizeof(dp));
    long long c[22];
    for(int i=1;i<22;i++)
        c[i]=i*i*i;
    dp[0]=1;
    for(int i=1;i<22;i++)
        for(int j=c[i];j<MAXN;j++)
        dp[j]+=dp[j-c[i]];
    while(cin>>n)
    {
        cout<<dp[n]<<endl;
    }
}

在合理选取递推式和状态时往往可以节省很多时间,提高效率。