题目描述
葱神喜欢K题,但在无限的K题生涯中总会出现特别无聊的日子。这种时候葱神就会通过为自己加上BGM来驱散无聊的情绪。作为一个特别看重K题手感的人,葱神拒绝通过在电脑上打开网易云音乐来听歌,这非常影响手感!取而代之的是,他通过磁带播放器来听歌。每当一个无聊的日子出现的时候,葱神就会找出一盘能录制N分钟音乐的磁带和M首歌。挑剔的葱神不会听两遍同一首歌,也不会一首歌听一半,现在你要编写程序帮助葱神挑选歌曲使得磁带剩下的空白时间最少。
数据约束:
N<65535 M<20
歌曲长度不超过N
输入
第一行为一个整数T,表示无聊的日子的数量
接下来T行,每行:
第一个数字N,代表磁带的容量,第二个数字M,代表歌曲的数量,随后M个数字,分别是这些歌曲的长度
输出
每一个无聊的日子对应两行
第一行为被选中的歌曲的长度(按输入顺序从后向前排列),简单起见最后一个数字后面有一个空格
第二行为最优解的歌曲总长
样例输入
5
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 40 12 9 8 2
样例输出
4 1
5
2 8
10
4 5 10
19
7 5 4 3 2 1 23 10
55
2 8 9 12 10 4
45
解题思路
先是常规的01背包,不压维,把磁带容量n作为背包容量,物品价值和花费都为歌曲长度。然后求01背包,得到最优歌曲总长。至于路径记录,可以用dfs。从dp[n][m]开始搜索,回忆一下求背包的过程,如果dp[n - 1][m] == dp[n][m],那么就说明第n件物品没有被选择(其实也可能被选择,但是当它没选肯定可以),那么就搜索到dp[n - 1][m],如果dp[n - 1][m] != dp[n][m],那就说明第n件物品被选择了,就输出这首歌的长度,然后搜索到dp[n - 1][m - v[n]]。接下来的同理,一直到n或者m等于0了,不能再往下找了,结束递归。其实就是一个逆过程。
代码如下
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define maxn 75535
using namespace std;
int dp[25][maxn];
int v[25]; //花费and价值
void dfs(int x, int y)
{
if(x == 0 || y == 0)
return;
if(dp[x][y] == dp[x - 1][y]) //相等说明没有选择x
dfs(x - 1, y);
else { //否则说明选择了
cout << v[x] << " ";
dfs(x - 1, y - v[x]);
}
}
int main()
{
int t;
scanf("%d", &t);
memset(dp, 0, sizeof(dp));
while(t --){
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i ++)
scanf("%d", &v[i]);
for(int i = 1; i <= m; i ++){ //求01背包
for(int j = 1; j <= n; j ++){
if(j - v[i] >= 0)
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + v[i]);
else
dp[i][j] = dp[i - 1][j];
}
}
dfs(m, n);
cout << endl;
cout << dp[m][n] << endl;
}
return 0;
}