“玲珑杯”线上赛 Round #15 河南专场:D -- 咸鱼商店


D -- 咸鱼商店

Time Limit:3s Memory Limit:256MByte

Submissions:220Solved:92

DESCRIPTION

你现在在咸鱼商店,你有M元钱。咸鱼商店有N个物品,每个物品有两个属性,一个是他的价格S[i],另外一个是他的价值V[i]。现在你想买一些物品,使得这些物品的价值和大于等于K,并且使得其中价值最低的商品的价值尽量高。请你输出这个最大价值。

INPUT
第一行三个整数N,M,K。接下来N行,每行两个整数S和V,分别表示价格和价值。满足:1 <= N, M, S <= 10^3, 0 <= V, K <= 10^6
OUTPUT
输出价值最低的商品能够达到的最大价值。如果无解,输出-1
SAMPLE INPUT
3 10 1
1 2
10 1
5 5
SAMPLE OUTPUT
5

解法1:贪心的策略,既然要总物品价值>=K中价值最低商品的所能达到的最大价值,所以想尽可能先挑价值大的。因此,先对物品的价值

按照从大到小排序,如果价值相同,按照花费从小到大排序。然后进行01背包,直到容量为M的背包所得价值大于等于M,输出该物品的价值。

虽然有好心人告诉我思路,可是我还是停了两天写的。最初有一个困惑想不明白,对于前i件物品,有一个dp[j]代表用容量为j的背包去装前i件

物品所能获得的最大价值,然后最后看dp[M]是否大于等于K,大于等于K输出第i件物品的价值,否则继续考虑将前i+1件物品放入背包,然后

想不通的地方是:对于背包容量M考虑将前i件物品装进去,dp[M]尽管满足要求,我感觉好像不能确定dp[M]所获得的价值到底包不包括第i件

物品的价值(即第i件物品到底装没装进背包)。想到这里非常困惑,不过闲下来好好想了想,想明白了。下面是关键代码:

for(int i = 1; i <= N; i++)  //考虑用背包装前i件物品去获得最大价值。
        {
            for(int j = M; j>= a[i].price; j--)
            {
                dp[j] = max(dp[j],dp[j-a[i].price]+a[i].value);
            }
            if(dp[M]>=K)
            {
                printf("%d\n",a[i].value);
                flag = false;
                break;
            }
        }

如果dp[i-a[i].price] + a[i].value > dp[j]。则说明第i件物品(也是最后一件物品,其价值最小)是装入背包了。

但是对于dp[i-a[i].price] + a[i].vale < dp[j]的情况,说明不装第i件物品,那么它的状态还是之前的状态。就是将

前i-1件物品装入容量为j的背包所得的最大价值,如果这种条件满足了>=K。则循环一定在变量循环到i之前就

break掉了。简单说根本就轮不到在考虑把前i件物品放到背包里,前面就已经有满足条件的解了。所以大佬提

供这样的思路特别有道理的。

AC代码:

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#define inf 0x3f3f3f3f

using namespace std;

const int maxv = 1003;
const int maxn = 1003;
int N,M,K;
int dp[maxv];
struct obj
{
    int price;  ///价格
    int value;  ///价值
}a[maxn];
bool cmp(struct obj a,struct obj b)
{
    if(a.value == b.value)  ///物品a,b价值相同
        return a.price<b.price;  ///按照价格从小到大排序
    else
        return a.value>b.value;  ///优先按价值排序
}
int main()
{
    while(~scanf("%d%d%d",&N,&M,&K))
    {
        for(int i = 1; i <= N; i++)
            scanf("%d%d",&a[i].price,&a[i].value);
        sort(a+1,a+N+1,cmp); ///优先选择按价值排序,价值相等按照花费从小到大排序
        memset(dp,0,sizeof(dp));
        bool flag = true;
        for(int i = 1; i <= N; i++)
        {
            for(int j = M; j>= a[i].price; j--)
            {
                dp[j] = max(dp[j],dp[j-a[i].price]+a[i].value);
            }
            if(dp[M]>=K)
            {
                printf("%d\n",a[i].value);
                flag = false;
                break;
            }
        }
        if(flag)
            printf("-1\n");
    }
    return 0;
}

思路2:官方题解给出二分+01背包的做法。所求答案无非是一个数,既然是一个数,这个数的范围就是1~inf的一个数,

这个题目给出物品最大价值是1e6,因此二分范围1~1e6即可,对于mid,对价值大于mid的所有物品进行一次01背包,

如果最后dp[M]>=K,让low = mid+1;否则high = mid-1,再次进行二分操作。

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#define inf 0x3f3f3f3f

using namespace std;

const int maxv = 1003;
const int maxn = 1003;
int N,M,K;
int dp[maxv];
struct obj
{
    int price;  ///价格
    int value;  ///价值
}a[maxn];
bool cmp(struct obj a,struct obj b)
{
    if(a.value == b.value)
        return a.price<b.price;
    else
        return a.value>b.value;
}
int Calculate_Value(int mid)
{
    memset(dp,0,sizeof(dp));
    for(int i = 1; i <= N; i++)
    {
        if(a[i].value>=mid)  ///只对价值大于等于mid的物体进行01背包
        {
            for(int j = M; j >= a[i].price; j--)
            {
                dp[j] = max(dp[j],dp[j-a[i].price]+a[i].value);
            }
            if(dp[M]>=K)
                return 1;
        }
        else break;
    }
    return 0;
}
int main()
{
    while(~scanf("%d%d%d",&N,&M,&K))
    {
        for(int i = 1; i <= N; i++)
            scanf("%d%d",&a[i].price,&a[i].value);
        sort(a+1,a+1+N,cmp);
        int low = 1;
        int high =  1e6;
        int ans = -1;
        while(low<=high)
        {
            int mid = (low+high)/2;
            int flag = Calculate_Value(mid);
            if(flag) ///dp[M]>=K
            {
                low = mid+1;
                ans = mid;
            }
            else
                high = mid-1;
        }
        printf("%d\n",ans);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值