P5662纪念品 洛谷题解

本文是一篇关于洛谷P5662纪念品问题的动态规划解题思路分享。作者首先指出问题可以通过动态规划解决,但初次尝试未成功。接着分析了如何将纪念品转化为利润关系,并将问题转化为完全背包问题。文章强调了如何处理跨天买卖以获取最大利润,并提供了关键的递推公式和代码实现,最终成功AC。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P5662纪念品 题解

前言

第一次发题解,多有瑕疵。
阅读题解之前务必熟悉题目

解题思路

考试刚看到题的时候,直觉便是动态规划,可硬是推了半天没推出来。(怪我太蒻了)

不过没关系,先把能写的写了…

int p[105][105];//存储每天每种纪念品的价格
int liRun[105];//存储每两天之间的利润(两天的价格差)

每件商品每天都有不一样的价格,而且每天可以进行无限次买卖,这很容易让人想到计算两天之间纪念品的差价(可获利润)。(like this)

 int t,n,m;cin>>t>>n>>m;//t天,n种纪念品,初始金币m个
 
 for (int i = 1;i <= t;i++)
  for (int j = 1;j <= n;j++)
   cin>>p[i][j];//读入每天的价格
   
 for (int i = 1;i <= t-1;i++)
  for (int j = 1;j <= n;j++)
   liRun[i][j] = p[i+1][j] - p[i][j];//单件纪念品的利润 

看出来了啥? 背包呀!

为什么可以这么分析呢?

每种纪念品我们都可以转换为利润之间的关系,我们就可以把金币的总数看作是背包的大小,每种纪念品当天的价值看作重量,利润就是每种纪念品的价值

我在思考的时候就提出了这样的疑问——假设一种商品要第一天买,第三天卖才可以获得最大利润,但是在第二天卖出又是第二天的最优解,该怎么办呢?
我们就不要考虑比较第利益的大小,因为我们是利用的是两天之间的这个过程,所以可以理解为第一天买进,第二天卖出再买进(这一步的实现来源于题目中给出每天每种物品可以买进卖出无限次,所以买来再卖掉,就可以理解为跳过了第二天,从而实现了最优解),然后再第三天再卖出。

所以这道题其实就是一个完全背包,当我们把纪念品当天的价格当作背包中物品的重量,把利润当作背包中的价值,如果我们用 f[v] 表示空间为v时的物品最大价值,那么我们可以得到:

f[v] = max(f[v],f[v - p[i][j] + liRun[i][j])

另一个需要注意的点是,背包的大小(手里的金币)是在不断增加的,所以每次求背包的最优解的时候,一定要排除价值为负数的纪念品(负利润),并且每隔一天刷新一下背包的大小(手里的金币),然后就可以得到我们的主程序了。

 for (int i = 1;i <= t-1;i++){//天数,利润的计算介于两天之间所以只用t-1天就好 
  for (int j = 1;j <= n;j++)//纪念品种数 
   if (liRun[i][j] > 0)//排除所有的负价值(负利润) 
    for (int v = p[i][j];v <= m;v++)
    //体积直接从 p[i][j]开始,反正之前的也放不下 ,一直遍历到当前手里的金币数量
     f[v] = max(f[v],f[v-p[i][j]] + liRun[i][j]);//完全背包求解 
  m += f[m];//更新手里的金币 
  memset(f,0,sizeof(f));//数组清0,开始第二轮背包 
 }

然后就AC了
最后给大家看一下完整的代码:

#include<iostream>
#include<algorithm>
#include<cstring> 
using namespace std;
int f[10005];// 背包
int p[105][105];//每天每种的价位
int liRun[105][105];//每两天之间的利润 
int main(){
 int t,n,m;cin>>t>>n>>m;//t天,n种纪念品,初始金币m个
 
 for (int i = 1;i <= t;i++)
  for (int j = 1;j <= n;j++)
   cin>>p[i][j];//读入每天的价格
   
 for (int i = 1;i <= t-1;i++)//天润的计算介于两天之间所以只用t-1天就好 
  for (int j = 1;j <= n;j++)
   liRun[i][j] = p[i+1][j] - p[i][j];//单件纪念品的利润 

 for (int i = 1;i <= t-1;i++){//天数,利润的计算介于两天之间所以只用t-1天就好 
  for (int j = 1;j <= n;j++)//纪念品种数 
   if (liRun[i][j] > 0)//排除所有的负价值(负利润) 
    for (int v = p[i][j];v <= m;v++)
     //体积直接从 p[i][j]开始,反正之前的也放不下 ,一直遍历到当前手里的金币数量
     f[v] = max(f[v],f[v-p[i][j]] + liRun[i][j]);//完全背包求解 
  m += f[m];//更新手里的金币 
  memset(f,0,sizeof(f));//数组清0,开始第二轮背包 
 }
 cout<<m;//输出 
 return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值