2016级算法第三次上机-C.AlvinZH的奇幻猜想——三次方
905 AlvinZH的奇幻猜想——三次方
思路
中等题。题意简单,题目说得简单,把一个数分成多个立方数的和,问最小立方数个数。
脑子转得快的马上想到贪心,从最近的三次方数往下减,反正有1^3在最后撑着保证减完。不好意思这是错的,因为1,27,64,125...等立方数之间并不是倍数关系,不能构成贪心策略。举个反例:96=64+8+8+8+8=64+27+1+1+1+1+1,答案明显是5,而贪心会算到7。
既然不是贪心,那就是DP了,没毛病。先讲一下常规做法吧,是这样想的:相当于把一个数化成几份,求最小划分的份数,那么把所求数看成背包的总体积,每个立方数的值看作每个物品的体积,价值都看做1,问题就转化为完全背包(因为每个立方数可以取多次)恰好装满求最小的价值,仔细想想。
所以,套一下完全背包的板子吧,但是,这里的一个问题是完全背包装满,这怎么办呢(可能由于本题可以保证装满这个问题不怎么显眼)。举一反三,假设有可能装不满,怎么办?这个问题初始化dp数组时就可以解决。以下方法对于01背包同样适用:
- 普通01背包or完全背包:初始化为0;
- 01背包or完全背包装满求价值最小:初始化为一个大数值如 \(INF\) (0x3f3f3f3f);
- 01背包or完全背包装满求价值最大:初始化为一个小数值如 \(-INF\) (-0x3f3f3f3f);
为什么呢?对于本题,初始化为大数值,如果没装满,最后dp[n]会依然是INF,因为我们每次比较取的都是较小值。第二次练习赛的G题就是01背包装满求最大价值,初始化为最小值即可。(熬夜写了这么多,希望大家看得到QAQ)
这题还有另外的解法,那就是类似打表,先把所有数的最小立方个数通过迭代计算得到,然后 \(O(1)\) 时间取得答案。具体可见队列迭代的参考代码二以及for循环迭代的参考代码三。其实这里面也是有DP的思想,因为每次迭代会用到之前的结果,对于多组数据来讲,同样可以节省时间。简单易懂,可以学习一下。
分析
对于完全背包直接解法,时间复杂度为 \(O(V*∑(V/wi))\) 。
迭代的话复杂度差不了多少,由于多组数据的原因,迭代打表运行总时间显得更短些,不纠结这个。
扩展:完全背包装满问题:HDU 1114。
参考代码一:完全背包装满求最小价值
// // Created by AlvinZH on 2017/10/24. // Copyright (c) AlvinZH. All rights reserved. // #include <cstdio> #include <cstring> #define INF 0x3f3f3f3f #define MaxSize 1000005 int weight[105]; int ans[MaxSize]; int main() { for (int i = 1; i < 105; ++i) weight[i] = i * i * i; int n; while(~scanf("%d", &n)) { for (int i = 0; i <= n; ++i)//初始化为最大值 ans[i] = INF; ans[0] = 0; for (int i = 0; i < 105; ++i) { for (int j = weight[i]; j <= n; ++j) { if(ans[j] > ans[j-weight[i]] + 1) ans[j] = ans[j-weight[i]] + 1; } } if(ans[n] == n) printf("Oh NO!\n"); else printf("%d\n", ans[n]); } } /* * 完全背包恰好装满问题,求最小值。 */
参考代码二:队列迭代
// // Created by AlvinZH on 2017/10/24. // Copyright (c) AlvinZH. All rights reserved. // #include <cstdio> #include <cstring> #include <queue> #define INF 0x3f3f3f3f #define MaxSize 1000005 using namespace std; int weight[105]; int ans[MaxSize]; queue<int> Q; void init() { memset(ans, INF, sizeof(ans)); for (int i = 1; i < 105; ++i) weight[i] = i * i * i; for (int i = 1; i <= 100; ++i) { ans[weight[i]] = 1; Q.push(weight[i]); } while(!Q.empty()) { int w = Q.front(); Q.pop(); for (int i = 1; i <= 100; ++i) { int num = weight[i] + w; if(num > 1000000) break; if(ans[num] < INF) continue; ans[num] = ans[w] + 1; Q.push(num); } } } int main() { init(); int n; while(~scanf("%d", &n)) { if(ans[n] == n) printf("Oh NO!\n"); else printf("%d\n", ans[n]); } } /* * 思路:直接宽搜,把最开始的数扔进队列,反复用队列中的数去更新没更新的数就行了,注意数组别越界。 */
参考代码三:for循环迭代
/* Author: 曾宥崴(13422) Result: AC Submission_id: 391756 Created at: Fri Nov 10 2017 18:26:40 GMT+0800 (CST) Problem: 905 Time: 353 Memory: 6612 */ #include <cstdio> #include <algorithm> #include <iostream> using namespace std; int f[1000001],n; int main() { for (int i = 1; i <= 1000000; i++) f[i] = 1000000000; f[0] = 0; for (int i = 0; i <= 1000000; i++) for (int k = 1; i + k * k * k <= 1000000; k++) f[i+k*k*k] = min(f[i+k*k*k],f[i] + 1); while (scanf("%d",&n) != EOF) if (f[n] == n) printf("Oh NO!\n"); else printf("%d\n",f[n]); return 0; }