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)}。
#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; }
浙公网安备 33010602011771号