背包与贪心

背包问题最经典的就是01背包。

n个物品,有体积 w 和价值 v 两种属性,选择一部分填充容量为 m 的背包(可以不填满),求能获得的最大价值。

其实分析下来,动态转移方程就是 f[i][v]=max{f[i-1][v], f[i-1][v-c[i]]+w[i]} ,优化后就是 f[v]=max{f[v], f[v-c[i]]+w[i]}  (第二重for循环从m -> v[i],为了不更新i-1层的状态)

这个状态转移方程就会一个贪心过程,不断的用第i个物品更新每个f[j]的值,找到最大的f[j],最终的结果就是最大值。

其实背包除了不一定恰好装满,恰好装满,还有大于等于背包的容量这种状态。像这种大于背包容量的最优解,不会求最大值(求最大值可以一直贪,无穷大),会求大于背包容量的最小花费(中文其实挺好玩的,求最大就说获得的价值,求最小就说是花费),这是一个零界点,具有最优性质在里面,所以用贪心去优化背包。


看一道2017年百度之星的题:

度度熊与邪恶大魔王


Problem Description

度度熊为了拯救可爱的公主,于是与邪恶大魔王战斗起来。

邪恶大魔王的麾下有n个怪兽,每个怪兽有a[i]的生命值,以及b[i]的防御力。

度度熊一共拥有m种攻击方式,第i种攻击方式,需要消耗k[i]的晶石,造成p[i]点伤害。

当然,如果度度熊使用第i个技能打在第j个怪兽上面的话,会使得第j个怪兽的生命值减少p[i]-b[j],当然如果伤害小于防御,那么攻击就不会奏效。

如果怪兽的生命值降为0或以下,那么怪兽就会被消灭。

当然每个技能都可以使用无限次。

请问度度熊最少携带多少晶石,就可以消灭所有的怪兽。

Input

本题包含若干组测试数据。

第一行两个整数n,m,表示有n个怪兽,m种技能。

接下来n行,每行两个整数,a[i],b[i],分别表示怪兽的生命值和防御力。

再接下来m行,每行两个整数k[i]和p[i],分别表示技能的消耗晶石数目和技能的伤害值。

数据范围:

1<=n<=100000

1<=m<=1000

1<=a[i]<=1000

0<=b[i]<=10

0<=k[i]<=100000

0<=p[i]<=1000

Output
对于每组测试数据,输出最小的晶石消耗数量,如果不能击败所有的怪兽,输出-1


这是一道完全背包,但是是n个背包,怪兽的生命力不同,防御值也不同,所以没有办法很好的去建立一个模型。

但是生命值1000种,防御值为10种,一共是10000种怪兽,我们求出这10000种怪兽的花费,在乘以该种类怪兽的数量,累加就是答案。这题的极限应该是O(1e9),但是数据应该不卡时间,所以200ms多就过了。

首先对m种技能进行排序(贪心策略),按防御力从小到大进行排序,防御力相同,花费大的在前(从劣往优排序),两重for,枚举怪物的种类,然后用m种物品更新最小花费。代码如下:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define LL long long
#define P pair<int,int>

using namespace std;

const int maxn=1e5+100,maxm=1e4+10;

int a[maxn],b[maxn];
LL f[maxm];
P c[maxm];
int n,m,ma,mb;

namespace std
{
bool operator < (const pair<int,int> &t,const pair<int,int> &s)
{
    return t.second==s.second?(t.first>s.first):(t.second<s.second);
}
}

LL solve()
{
    int t=1;

    for(int i=2; i<=m; ++i)
    {
        while(t>0&&c[t].first>c[i].first) --t;
        c[++t]=c[i];
    }

    LL ans=0;
    for(int i=0; i<=mb; ++i)
    {
        for(int j=1; j<=ma; ++j)
        {
            f[j]=1ll*j*c[t].first;
            for(int k=1; k<=t; ++k)
                if(c[k].second>i)
                    f[j]=min(f[j],f[max(0,j+i-c[k].second)]+c[k].first);
        }
        for(int j=1; j<=n; ++j)
            if(b[j]==i) ans+=f[a[j]];
    }
    return ans;
}

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        ma=0,mb=0;
        for(int i=1; i<=n; ++i)
        {
            scanf("%d%d",&a[i],&b[i]);
            if(a[i]>ma) ma=a[i];
            if(b[i]>mb) mb=b[i];
        }

        for(int i=1; i<=m; ++i)
            scanf("%d%d",&c[i].first,&c[i].second);

        sort(c+1,c+m+1);
        if(c[m].second<=mb)
        {
            printf("-1\n");
            continue;
        }
        /*
                for(int i=1;i<=m;++i)
                    printf("%d %d\n",c[i].first,c[i].second);
        */
        printf("%I64d\n",solve());
    }
    return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值