2017百度之星资格赛 hdu6083 度度熊的午饭时光 (01背包+字典序路径)

题意:

度度熊最期待每天的午饭时光,因为早饭菜品清淡,晚饭减肥不敢吃太多(胖纸的忧伤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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值