题目:
1 1000 5 800 2 400 5 300 5 400 3 200 2
3900
这是一道典型的动态规划的题目。先来分析下这道题。这道题有两个状态,i:挑选过了多少个物品,j:拥有多少钱。全局的最优解必定包含局部最优解,所以,我们不妨从局部开始着手。假设总和存储在count[25][30000],每个物品的价格和重要度对应的存在数组M[25], P[25]中。count[1][1000]表示挑选了一个物品的时候物品的价格和重要度的总和。当count[0][j]是很明显应该全部都是0,因为没有物品可以挑选了,无论有多少钱都是0。然后从局部的开始考虑:先给出递推公式:count[i][j] = max(count[i - 1][j], count[i - 1][j - M[i - 1] ] + M[i - 1] * P[i - 1]),第二个表达式:count[i - 1][j - M[i - 1] ] + M[i - 1] * P[i - 1],代表的是购买第i个物品,显然,第一个就是不买的意思。而第二个表达式的M[i -1]和P[i - 1]的下标之所以是(i - 1)是因为M[0]和P[0]对应的是第一个物品的价格和重要度。
那好了,当i = 1的时候我们可以局部的考虑最优的购买方案。当i = 2的可以通过max来获得这时够不够买来第二个来获得最优解。(当然,前提是钱要足够~)
还是不是很懂的可以看我的这篇博文:http://blog.csdn.net/sky453589103/article/details/43735005
好了现在应该就可以上代码了:
#include <iostream> #include <string.h> using namespace std; long count[30][30001]; int M[30]; int P[30]; int main() { int n; cin >> n; int N, m; while(n--) { cin >> N >> m; int i = 0, j = 0; memset(count, 0, sizeof(count)); memset(M, 0, sizeof(M)); memset(P, 0, sizeof(P)); for(i = 0; i < m; ++i) { cin >> M[i] >> P[i]; } for(i = 1; i <= m; ++i) { for(j = 0; j <= N; ++j) { count[i][j] = count[i - 1][j]; if(j >= M[i - 1]) { long tmp = count[i - 1][j - M[i - 1]] + M[i - 1] * P[i - 1]; if(tmp > count[i][j]) { count[i][j] = tmp; } } } } cout << count[m][N] << endl; } return 0; }
AC之后之后发现,内存好大!是不是该想想怎么优化呢?
首先,让我们先考虑数组M, P。这两个数组其实可以换成两个变量,因为可以边输入边挑选嘛。
然后我们再来想想最严重的问题:数组count。
看看下面的代码:
for(i = 1; i <= m; ++i) { for(j = 0; j <= N; ++j) { count[i][j] = count[i - 1][j]; if(j >= M[i - 1]) { long tmp = count[i - 1][j - M[i - 1]] + M[i - 1] * P[i - 1]; if(tmp > count[i][j]) { count[i][j] = tmp; } } }观察之后,我们可以发现,我们每次都只要用到count[i -1]这一维去更新count[i]这一维的内容,所以我们可不可以只用一个一维数组去保存我们要的信息呢?
答案的是可以的。这里,我们用一个滚动数组来优化。
这样一维数组 count[j] 就代表着挑选第 i 个物品的时候的最优解(即没有优化的时候的count[i][j])。值得注意的是,如果是还没在当前i下更新count[j]话,那么count[i]代表的是count[i - 1]即挑选上一个物品的状态(即没有优化的时候的count[i -1][j]),这就是滚动数组的神奇之处,整个数组会不断的更新、滚动。但是这样的话还是会有bug,因为我在就j + 1的时候还是要用到i - 1的状态,但是 i - 1已经在j 的时候,被更新了。也就是说,在没有优化的情况下的count[i-1][j] 被更新成了count[i][j]
怎么办呢?
既然顺序不行,那么我们就好考虑一下逆序吧。
重新考虑上面那部分短的代码,你会发现当我们没有优化的时候,j 的循环次序是没有关系的,注意到与每个当前状态有关的是 count[i - 1]那一维的。也就是说,当我们没有优化的时候,逆序循环j是正确的。好了,还是先看看优化之后的代码吧:
#include <iostream> #include <string.h> using namespace std; long count[30000]; int main() { int M, P; int n; int N, m; cin >> n; while(n--) { cin >> N >> m; memset(count, 0, sizeof(count)); int i = 0, j = 0; for(i = 0; i < m; ++i) { cin >> M >> P; for(j = N; j >= M; --j) { if(count[j] < count[j - M] + M * P) { count[j] = count[j - M] + M * P; } } } cout << count[N] << endl; } }
for(j = N; j >= M; --j) { if(count[j] < count[j - M] + M * P) { count[j] = count[j - M] + M * P; } }当我们逆序循环j的时候,很明显的count[j-M]的值还没被更新,也就是说它的值还是上一个i对应的值。但是当我们顺序循环的时候,就是不是这样了,count[j - M]已经被更新成当前i对应的值了。然后,还有注意到的就是拥有的钱要大于当前物品的加个才嫩考虑购买物品。所以,我们主要循环到M就行了(注意,可以等于M)。
这样,这道题优化答案就出来了。