题目
样例输入
5 3 1 3 4
10 4 9 8 4 2
20 4 10 5 7 4
90 8 10 23 1 2 3 4 5 7
45 8 4 10 44 43 12 9 8 2
样例输出
1 4 sum:5
8 2 sum:10
10 5 4 sum:19
10 23 1 2 3 4 5 7 sum:55
4 10 12 9 8 2 sum:45
思路
一个0/1背包问题,二维数组f[ i ][ j ]记录前 i 个物品放进容量为 j 的背包中的最大价值。
状态转移方程为。
我觉得,这道题关键问题在于路径记录。
路径记录
法一:通过回退寻找路径,如果f[ i ][ cash ] == f[ i - 1 ][ cash ],说明没有用到第 i 张CD,直到回退到f[1]。
法二:申请二维bool数组,在计算 f 数组的时候,如果在f[ i ][ j ]时采用第 i 张CD,则记录到bool数组中,进行回退。
两个方法计算方法答案可能有所不同(在出现多组答案时),将两组代码都po出来,都可以过oj(为什么写法二呢,因为法一对不上样例,我是写完法二才发现原来法一早就过了oj...)。
代码
法一
#include<iostream>
#include<algorithm>
using namespace std;
int N, M;
int w[22];
int packet[25][10010];
bool is[22];
void traceback()
{
int c = N;
for (int i = M; i >= 1; i--)
{
if (packet[i][c] == packet[i - 1][c]) is[i] = 0;
else
{
is[i] = 1;
c -= w[i];
}
}
for (int i = 1; i <= M; i++)
if (is[i]) cout << w[i] << " ";
}
int main()
{
while (cin >> N >> M)
{
for (int i = 1; i <= M; i++)
cin >> w[i];
for (int i = 0; i <= N; i++) packet[0][i] = 0;
for (int i = 1; i <= M; i++)
{
for (int j = 0; j <= N; j++)
{
packet[i][j] = packet[i - 1][j];
if (j - w[i] >= 0)
packet[i][j] = max(packet[i][j], packet[i - 1][j - w[i]] + w[i]);
}
}
traceback();
cout << "sum:" << packet[M][N] << endl;
}
}
法二
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int N, M;
int w[22];
int packet[25][10010];
bool is[22];
bool iss[25][10010];
void traceback()
{
int c = N;
for (int i = M; i >= 1; i--)
{
if (!iss[i][c]) is[i] = 0;
else
{
is[i] = 1;
c -= w[i];
}
}
for (int i = 1; i <= M; i++)
if (is[i]) cout << w[i] << " ";
}
int main()
{
while (cin >> N >> M)
{
memset(iss, 0, sizeof iss);
memset(is, 0, sizeof is);
for (int i = 1; i <= M; i++)
cin >> w[i];
for (int i = 0; i <= N; i++) packet[0][i] = 0;
for (int i = 1; i <= M; i++)
{
for (int j = 0; j <= N; j++)
{
packet[i][j] = packet[i - 1][j];
if (j - w[i] >= 0 && packet[i - 1][j - w[i]] + w[i] >= packet[i][j])
{
packet[i][j] = packet[i - 1][j - w[i]] + w[i];
iss[i][j] = 1;
}
}
}
traceback();
cout << "sum:" << packet[M][N] << endl;
}
}