《训练指南》——6.9
Uva11137:
立方体之和:输入正整数n(n≤10000),求将n写成若干个正整数的立方体之和有多少种方法。
分析:这道题目其实就是基于递推的一道动态规划题目了,我们建立记录多状态的多段图,利用d[i][j]来表示立方数的底数不超过i且最终和为j的方法数,结合n的取值,有21^3 > 10000,因此这里的最终答案便是d[21][n].
下面我们面临的问题就是如何求解d[i][j],即寻求递推关系。对于d[i][j],我们分成如下的两种情况:
(i)和式当中最大的底数是i-1,则有d[i-1][j]种情况。
(ii)和式当中最大的底数是i,则有d[i][j-i^3]种情况。
可以看到,d[i][j]基于的子状态两个维度的参数都小于或等于i、j,这放在动态规划当中,叫做无后效性。
#include<cstdio> #include<cstring> using namespace std; int main() { long long d[23][10005]; memset(d , 0 , sizeof(d)); d[0][0] = 1; for(int i = 1;i < 23;i++) for(int j = 0;j < 10005;j++) { if(j-i*i*i >= 0) d[i][j] = d[i-1][j] + d[i][j-i*i*i]; else d[i][j] = d[i-1][j]; } // printf("%d\n",d[1][0]); int n; while(scanf("%d",&n) != EOF) { printf("%lld\n",d[22][n]); } }
Uva11375(需要高精度,还没有A):
火柴:用n(1≤n≤2000)根能够组成多少个非负整数?
分析:我们设置d[i]恰好用i根火柴摆出不同的非负整数的个数。F[n]是本题结果,则显然有F[n] = ∑d[i]。因此现在我们面临的问题是如何计算d[n]。
我们用c[x]记录拼出个位数x所需要的火柴数目,那么现在考察d[n]中的所有情况,为了便于找到递推关系,我们都去掉每一个整数的个位,这样我们能够找到如下的状态转移方程,或者说是递推方程:
d[n] = ∑d[n-c[i]],i∈[0,9].
但是在编码实现的时候,并不是线性得求出d[1],d[2]…d[n],而是动态的更新d[1]、d[2]…的值,从状态转移方程中能够看出,d[n]需要基于更小规模的子问题d[n-c[i]],因此这里我们首先设置一层循环遍历1~n作为d[]下标的参数,然后再进行“添加尾数”的操作,这样就有如下的状态转移方程:
for i 0 to maxn
for j 0 to 9
d[i+c[j]] += d[i]
简单的参考代码如下(Ps原题结果较大应用高精度运算):
#include<cstdio> #include<cstring> using namespace std; const int maxn = 2005; int main() { int c[10] = {6,2,5,5,4,5,6,3,7,6}; int d[maxn]; memset(d , 0 , sizeof(d)); d[0] = 1; for(int i = 0;i < maxn;i++) for(int j = 0;j < 10;j++) if(!(i==0 && j==0) && i + c[j] < maxn) d[i+c[j]] += d[i]; int n; while(scanf("%d",&n) != EOF) { int sum = 0; for(int i = 1;i <= n;i++) sum += d[i]; printf("%d\n",sum); } // printf("%d %d %d %d",d[2],d[3],d[4],d[5]); }