Codeforces ProblemSet 19B 详解

19B 解题思路分析

题目大意:
给出一些商品,每一件商品有两个属性:ti和ci,表示以ci的代价购买了该商品之后,你可以从其他的物品中无偿(不支付代价)地挑选出ti个物品。求能够获得所有商品的最小代价。

思考过程:
看到这道题的时候,我就觉得一边买东西一边送东西,这事儿肯定不对。如果两件事情放在一起考虑的话就会让自己的解题思路陷入混乱之中,那么这道题我把它分为两部分去分析:先考虑比较简单的那部分,只买东西,不考虑送东西的情况。

如果在只考虑买东西的情况下,这道题就是明显的01背包问题,每一个状态的决策只有买与不买这两种,则设置状态为f[i][j]以表示考虑到第i件物品,已经购买了j件物品所需要的最小价值,状态转移方程f[i][j]=min(f[i-1][j-1]+c[i],f[i-1][j]),初始状态f[0][0]为0。

接下来就应该分析问题的另一部分了。做动态规划类问题的时候,我最经常在脑海里浮现的词语无异于“等价于”、“相当于”、“同”……因为这些词语后面所跟着的那段句子,往往才是解决这道题的关键所在,知道了由这几个词语引导的这句话,NOIP的基础动态规划问题就基本解决多了一大半。在这道题目当中,因为买第i件商品的时候会赠送ti件商品,而且这ti件商品又是可以随意挑选的,所以ti+1(算上这个物品本身)就是花了ci的代价之后能获得的商品的数量,也就是说这就是这个物品对答案的“贡献”,这也就相当于这个物品的体积。则共有n个物品就相当于背包的容量为n。

说到这,这道题看起来就变成了一道背包问题?不是的!再次分析刚刚的等价条件和问题之间的关系就可以发现,题目的要求是要求把容量为n的背包装满甚至溢出来(后面会解释)的情况下所需要的最小代价和。状态转移方程也和刚刚的很相像:
f[i][j]=min(f[i-1][j-(t[i]+1)]+c[i],f[i-1][j])。

代码实现:
既然已经将这道题转化到了背包问题模型中去,下面就要考虑如何转移这些状态:其实这道题在分析的过程中已经说过:转化过的问题极似01背包问题,所以说外层循环枚举商品种类,内层循环(倒序)枚举背包的容量以防止重复选中。这里还要解释为什么背包里的物品体积为什么可以大于n,因为这里的n表示已购买了n个物品,大于n的情况只可能来自赠送的商品数大于剩余商品数的情况,所以满足题意,就应该被计入答案。

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstring>
#include<iostream>
#include<queue>
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define abs(a) ((a)<(0)?-(a):(a))
using namespace std;
int n;
int c[2005];
int t[2005];
long long f[2005];
#define neg(a) ((a)<0?0:(a))
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&t[i],&c[i]);
    for(int i=1;i<=n;i++) f[i]=(1LL<<60);
    f[0]=0LL;
    for(int i=1;i<=n;i++)
        for(int j=n;j>=1;j--)
            f[j]=min(f[j],f[neg(j-1-t[i])]+c[i]);
    printf("%I64d",f[n]);
    return 0;
}

转载于:https://www.cnblogs.com/BulaBulaCHN/p/6224532.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值