题意:
度度熊最期待每天的午饭时光,因为早饭菜品清淡,晚饭减肥不敢吃太多(胖纸的忧伤T.T)。
百度食堂的午餐超级丰富,祖国各大菜系应有尽有,度度熊在每个窗口都有爱吃的菜品,而且他还为喜爱的菜品打了分,吃货的情怀呀(>.<)。
但是,好吃的饭菜总是很贵,每天的午饭预算有限,请帮度度熊算一算,怎样打饭才能买到的最好吃的饭菜?(不超过预算、不重样、午餐等分最高的情况下,选择菜品序号加和最小,加和相等时字典序最小的组合)
输入
第一行一个整数T,表示T组数据。
每组测试数据将以如下格式从标准输入读入:
B
N
score_1 cost_1
score_2 cost_2
:
score_N cost_N
第一行,正整数B(0 <= B <= 1000),代表午餐的预算。
第二行,正整数N (0 <= N <= 100),代表午餐可选的菜品数量
从第三行到第 (N + 2) 行,每行两个正整数,以空格分隔,score_i表示菜品的得分,cost_i表示菜品的价格(0 <= score_i, cost_i <= 100)。
输出:
对于每组数据,输出两行:
第一行输出:”Case #i:”。i代表第i组测试数据。
第二行输出菜品的总得分和总花费,以空格分隔。
第三行输出所选菜品的序号,菜品序号从1开始,以空格分隔。
分析:
这道题唯一的难点就在于字典序最小,之前没有做过这类题目,现将方法记录一下。
《背包九讲》中介绍了一种简单的方法:
这里“字典序最小”的意思是 1 … N 号物品的选择方案排列出来以后字典序最小。
以输出 01 背包最小字典序的方案为例。
一般而言,求一个字典序最小的最优方案,只需要在转移时注意策略。
首先,子问题的定义要略改一些。我们注意到,如果存在一个选了物品 1 的最优方
案,那么答案一定包含物品 1,原问题转化为一个背包容量为 V − C1,物品为 2 … N
的子问题。反之,如果答案不包含物品 1,则转化成背包容量仍为 V ,物品为 2 … N
的子问题。
不管答案怎样,子问题的物品都是以 i … N 而非前所述的 1 … i 的形式来定义的,
所以状态的定义和转移方程都需要改一下。
但也许更简易的方法是,先把物品编号做 x ← N + 1 − x 的变换,在输出方案时再
变换回来。在做完物品编号的变换后,可以按照前面经典的转移方程来求值。只是在输
出方案时要注意,如果 F[i, v] = F[i − 1, v] 和 F[i, v] = F[i − 1][v − Ci
] + Wi 都成立,
应该按照后者来输出方案,即选择了物品 i,输出其原来的编号 N − 1 − i。
简单来说,就是尽可能地让每个状态都通过 f[i][j] = f[i][j - c[i]] + v[i] 转移。将序号反过来,从 n 开始倒序(这里的 n 实际上就是原来顺序的第 1 个)。先判断最终状态能不能通过用上第 n 个来转移到,若可以,就用上(实际是用上了第 1 个)。同理,再判断通过第 n 个转移之前的状态能不能通过第 n - 1 个转移到,若能就用上。以此类推直至结束。
当然,输出最小字典序还有很多方法。另一种常见的是记录下来每一次状态的转移,用path[i][j]记录这个状态是由哪个点转移过来的,最后逆序推回去输出。
/**********记录***************/
for (int i = 1; i <= n; i++) {
for (int j = B; j >= 0; j--) {
f[i][j] = f[i - 1][j];
sum[i][j] = sum[i-1][j];
path[i][j] = path[i-1][j];
if(j >= c[i]){
if (f[i][j] < f[i - 1][j - c[i]] + v[i]) {
f[i][j] = f[i - 1][j - c[i]] + v[i];
sum[i][j] = sum[i - 1][j - c[i]] + n+1-i;
path[i][j] = i;
} else if (f[i][j] == f[i - 1][j - c[i]] + v[i]) {
if(sum[i][j] > sum[i-1][j-c[i]]+n+1-i){
sum[i][j] = sum[i-1][j-c[i]]+n+1-i;
path[i][j] = i;
}
}
}
}
}
/***********递归输出***************/
void Output(int i,int j)
{
int k=path[i][j];
if(dp[k-1][j-v[k]]==0) printf("%d\n",n-k+1);
else
{
printf("%d ",n-k+1);
Output(k-1,j-v[k]);
}
}
很显然,这种方法代码量略大于第一种方法,而且需要一个额外的二维数组。
代码:
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int MAXN = 105;
const double EPS = 1e-8;
const int INF = 0x3f3f3f3f;
int n, B, f[MAXN][MAXN * 10], v[MAXN], c[MAXN], sum[MAXN][MAXN * 10];
int main() {
// freopen("1.txt","w",stdout);
int T, kase = 1;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &B, &n);
for (int i = n; i >= 1; i--) {
scanf("%d%d", &v[i], &c[i]);
}
ms(f, 0);
ms(sum, INF);
vector<int> path;
for(int i=0;i<MAXN*10;i++) sum[0][i] = 0;
for(int i=0;i<MAXN;i++) sum[i][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = B; j >= 0; j--) {
f[i][j] = f[i - 1][j];
sum[i][j] = sum[i - 1][j];
if(j >= c[i]){
if (f[i][j] < f[i - 1][j - c[i]] + v[i]) {
f[i][j] = f[i - 1][j - c[i]] + v[i];
sum[i][j] = sum[i - 1][j - c[i]] + n+1-i;
} else if (f[i][j] == f[i - 1][j - c[i]] + v[i]) {
sum[i][j] = min(sum[i][j], sum[i - 1][j - c[i]] + n+1-i);
}
}
}
}
if(f[n][B] == 0){
printf("Case #%d:\n0 0\n",kase++);
}else{
int V = B;
for(int i=n;i>=1;i--){
if(f[i][V] == f[i-1][V-c[i]]+v[i] && sum[i][V] == sum[i-1][V-c[i]]+n+1-i){
path.push_back(n+1-i);
V -= c[i];
}
}
int cost = 0;
for(vector<int>::iterator it=path.begin();it!=path.end();it++){
cost += c[n+1-*it];
}
printf("Case #%d:\n%d %d\n",kase++,f[n][B],cost);
for(vector<int>::iterator it=path.begin();it!=path.end();it++){
if(it!=path.end()-1) printf("%d ",*it);
else printf("%d\n",*it);
}
}
}
return 0;
}