题意: 经典的 01 背包问题
思路: 基本的做法是用 best[i][j] 表示前 i 个奖品在奖券小于等于 j 的情况下能达到的最大喜好值。每个奖品都有取或不取两种状态,取的时候奖券变少,喜好值增加;不取的时候奖券和喜好值都不变。所以此时的状态转移方程式为
best[i][j]=max(best[i−1][j],best[i−1][j−need(i)]+value[i])
写成代码为:
memset(best[0], 0, sizeof(best[0]));
for(int i=1; i<=N; i++){
for(int j=0; j<=M; j++){
if(j < need[i]) best[i][j] = best[i-1][j];
else best[i][j] = max(best[i-1][j], best[i-1][j-need[i]]+value[i]);
}
}
由状态转移式我们可以看出求 best[i][j] 时只与第 i-1 行有关系,所以这里继续进行优化以减少空间。
当我们把第二层循环反过来时,也就是从 M 到 0 进行遍历时,会发现 best[i][j] 的值是从右往左计算的,而计算时只与当前位置的 best[i-1][j] 和左边的 best[i-1][j-need[i]] 有关,所以这边又可以进行空间上的优化,使得只需要 O(M) 的空间复杂度。
又从 need[i] 遍历到 0 时 best[i][j] 的值就等于 best[i-1][j] 的值,所以可以不需要进行遍历。
优化的代码见下列程序。
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<stack>
#include<queue>
#include<utility>
#include<vector>
#include<cmath>
#include<set>
#include<map>
#include<iostream>
#include<algorithm>
#include<sstream>
using namespace std;
typedef long long LL;
int N, M;
int need[550];
int value[550];
int best[100010];
int main()
{
while(scanf("%d%d", &N, &M) == 2){
for(int i=1; i<=N; i++){
scanf("%d%d", &need[i], &value[i]);
}
memset(best, 0, sizeof(best));
for(int i=1; i<=N; i++){
for(int j=M; j>=need[i]; j--){
best[j] = max(best[j], best[j-need[i]]+value[i]);
}
}
printf("%d\n", best[M]);
}
return 0;
}