PKU POJ 1664 放苹果 DP

题目意思很明确:给定一个正整数n,求将其划分为m份的不同方法数。

这里是整数分割(整数划分)的方法。

动态规划方法

(1) 最原始的动态规划:考虑划分过程中的最大数 k,令dp[i][j][k]表示将整数j划分成 i 份,而最后一个数最大为 k 的划分方案总数。那么:该状态(i, j, k)可由前面“i-1”份的“j-k”加上“一份k”而来,因此当前状态的方案总数就等于“j-k”划分为i-1份的所有最大加数小于等于k的方案数至和。得到状态转移方程:dp[i][j][k] = SUM{ dp[i-1][j-k][l] | 0 <= l <= k }

DP算法1: O(n^3*m)

Set all dp[i][j][k] = 0;
dp[0][0][0] = 1;
for(i = 1; i <= m; i++)
    for(j = i; j <= n; j++)
        for(k = 1; k <= j; k++)
            for(l = 0; l <= k; l++)
                dp[i][j][k] += dp[i-1][j-k][l];
for(i = 1, cnt = 0; i <= n; i++) cnt += dp[m][n][i];
return cnt;

可见,这是一个O(n^3*m)的算法,仅适合规模小的情况

(2) 改进这个规划算法,减少第3维的状态,i,j的状态含义有改变,它们此时的含义就是:将整数i划分为j份的方案数。根据类似于“堆积木”的思维,当前状态实际上就是现在最下一行各摆上“1”块积木接下来就是把“i-j”块积木放上去并保持阶梯状,实际就是“i-j”拆分成“0~k(k <= j)”份的方案总数之和,所以有:dp[i][j] = SUM{ dp[i-j][k] | 0 <= k <= j}

Set all dp[i][j] = 0;
dp[0][0] = 1;
for (i = 1; i <= n; i++)
    for (j = 1; j <= (i > m ? m : i); j++)
        for (k = 0; k <= j; k++)
            dp[i][j] += dp[i-j][k]
return dp[n][m];

可见,这是一个O(n*m^2)的算法,仍然不怎么适用于大规模的n, m

(3) 是否可以继续化简呢?这里我们从方程入手:dp[i-1][j-1] = SUM{ dp[i-1-j+1][k] | 0<=k<=j-1}

= SUM { dp[i-j][k] | 0<=k<=j-1 }

观察下标k的变化,方程可简化为:dp[i][j] = dp[i-1][j-1] + dp[i-j][j]

Set all dp[i][j] = 0;
dp[0][0] = 1;
for(i = 1; i <= n; i++)
    for(j = 1; j <= (i > m ? m : i);j++)
        dp[i][j] = dp[i-1][j-1] + dp[i-j][j];
return dp[n][m];

可见,时间复杂度降为Min{O(n^2), O(nm)}。

本题搜索页可以做,速度较慢,这里用DP。
#include<iostream>
using namespace std;
int main()
{
    int i,j,dp[11][11];
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    for (i=1;i<11;++i)
        for (j=1;j<=i;++j)
            dp[i][j]=dp[i-1][j-1]+dp[i-j][j];
    int t,m,n,cnt;
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d",&m,&n);
        cnt=0;
        for (j=0;j<=n;++j)
            cnt+=dp[m][j];
        printf("%d\n",cnt);
    }
    return 0;
}

 

posted on 2013-03-04 18:21  Deller  阅读(245)  评论(0)    收藏  举报

导航