题意:

\(n\)个物品,每个物品有一个价格,买一个高价格的物品,可以选择免费得到一个价格严格低于这个物品的物品。求得到\(n\)个物品的最小代价。

题解:

神仙贪心……

题目要求求出最小代价,相当于求最多能免费拿的价格。

先考虑一个\(n^2\)的DP:将物品按价格从高到低排序。把相同价格的物品放在一起处理。\(f[i][j]\)表示DP到第\(i\)位,免费拿了\(j\)个物品,最大能节约的价格。

那么已经原价买了但还没有送东西的物品有\(i - 2 * j\)个,我们称这些物品为可用物品。

有2种转移:

1,使用可用物品,免费拿当前物品(买一送一)

2,买走一个之前免费拿的物品,用腾出来的可用物品和新买的物品免费拿2个当前物品(买一送二)

但复杂度\(n^2\),无法通过此题。

现在来考虑优化:

我们对原来的DP数组进行差分得到\(g[i][j] = f[i][j] - f[i][j - 1]\).

也就是说\(g[i][j]\)表示的不是实际上的值,而是与上一次相比的增量。

既然对于每次转移,我们可以知道相比与上次的增量,那么我们考虑是否可以通过贪心来解决这个问题。

可以发现,\(g[i][j]\)一定单调不增,我们考虑通过3种操作来依次更新\(g\)数组.(2,3步贪心的正确性是基于已经完成了操作1的基础上的)

1,如果还有多余的可用物品,那么先把当前物品中能免费拿的都拿走。

2,获取\(g[i][j - 1]\)的增量\(x\),如果\(x < val_{now}\)那么选择免费拿走带来这个增量\(x\)的物品\(y\),我们买走它,因为\(x\)和腾出来的那个可用物品的价格都严格大于当前物品,因此我们可用最多免费拿2个当前物品放入\(g\)数组来更新增量,但也有可能只能放入一个,因为当前循环枚举的上限是将之前的所有物品都买下,能够免费拿走多少当前物品,而这个上限可能是奇数,所以在最后面如果拿2个可能就超过上限了。增量即为 免费拿的个数*当前物品价格。

3,增量\(x > val_now\),那么\(x\)现在还比较优秀,我们继续选择在\(g[i][j - 1]\)中免费拿走它,但对于\(g[i][j]\),如果在这个状态中,如果我们选择买走\(y\),然后用腾出来的物品和\(y\)免费拿走2个当前物品,是有可能更优的(因为不这么做就无法拿更多的物品),这样操作带来的增量可以表示为\(2 * s[now] - x\),如果这个增量大于0,那么我们选择用这个增量来更新\(g[i][j]\)。

用堆来维护即可。最后的答案即为总价格 - \(\sum{g[n][j]}\)(因为在上述转移中,每次转移都只有增量大于0我们才去更新\(g\)数组,即将真实的\(g\)数组对0取max了,因此\(\sum{g[n][j]} = max(f[n][j])\))

有一个容易产生疑惑的地方:为什么2中的增量是直接用数量×价格,而3中却要减去上一步的增量\(x\)呢?

可以观察到,这2个有一个本质上的区别:2虽然修改了\(g[i][j - 1]\),但从拿物品的方案上而言,是承接了\(g[i][j - 2]\)的,所以不必因为替换了一些物品而减小增量,毕竟\(g[i][j - 2]\)的那个增量是还在的。

而3在更新\(g[i][j]\)的过程中,所使用的方案,和\(g[i][j - 1]\)的方案并不一样,在\(g[i][j - 1]\)中,我们免费拿走了物品\(y\),带来了增量\(x\),而在\(g[i][j]\)中,我们买了物品\(y\),失去了增量\(x\),这样的话,虽然我们带来了\(2 * s[now]\)的增量,但相对于\(f[i][j - 1]\),我们的\(f[i][j]\)并没有增加这么多,因此总的增量为\(2 * s[now] - x\)

如果还有不懂的就看看官方题解 or 代码吧。

我感觉这题的代码应该是比较清晰的。(不喜欢注释的可以把注释删掉再看QWQ)

(如果您觉得我有地方理解错了,还请指出……毕竟这题比较神仙)

#include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 501000
#define LL long long int n, tot, sum, rnt;
int cnt[AC], s[AC], tmp[AC];
LL ans; struct cmp1{bool operator() (int a, int b){return a > b;}};//堆要用小根堆,因为f[j]必定递减,且优先替换便宜的肯定要优一些
inline bool cmp(int a, int b){return a > b;} priority_queue<int, vector<int>, cmp1> q; inline int read()
{
int x = 0;char c = getchar();
while(c > '9' || c < '0') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x;
} void pre()
{
n = read();
for(R i = 1; i <= n; i ++) s[i] = read(), ans += s[i];
sort(s + 1, s + n + 1, cmp);
for(R i = 1; i <= n; i ++)//去重
if(s[i] != s[i + 1]) s[++ tot] = s[i], ++ cnt[tot];
else ++ cnt[tot + 1];
} LL t[AC], top;
inline void check()
{
top = 0;
while(!q.empty()) t[++ top] = q.top(), q.pop();
for(int i = top; i; i --) printf("%lld ", t[i]), q.push(t[i]);
if(top) printf("\n");
else printf("0\n");
} void work()
{
for(R i = 1; i <= tot; i ++)
{
int have = sum - 2 * q.size();//获取现在不做任何修改可以免费送的物品
have = min(have, cnt[i]);//只能放下来取min,因为q.size默认是unsigned int……
for(R j = 1; j <= have; j ++) tmp[++ rnt] = s[i];//先把可以送的直接送了,单独放在一个数组里,防止和之前那些严格比当前大的混在一起
have = min(sum, cnt[i]) - have;//获取最多可以送几个(在当前已经送出一部分的前提下做修改)
for(R j = 1; j <= have; j += 2)//have重新赋值为最多还可以送的个数,因为每次会塞进来2个数,所以每次加2个
{//(min(sum, cnt[i])是把之前的全都买了可以送当前物品的个数,再减去已经送了的)
LL x = q.top(); q.pop();
if(x < s[i])//如果替换掉这一位更优,那么就换掉
{
tmp[++ rnt] = s[i];
if(j < have) tmp[++ rnt] = s[i];//如果j = have,只能说明是奇数,所以不能放第2个进来,,,
}
else//否则的话就维持之前的方案,并且由于无法替换(下一位置是0),只能尝试买下x,送2个s[j]
{
tmp[++ rnt] = x;//这个也要放到临时数组,,,不然就永远取不出更高的了
if(j < have && 2 * s[i] - x > 0) tmp[++ rnt] = 2 * s[i] - x;//下一状态就买下x,送2 个 s[i],不过如果没有增益的话,还不如不送
}
}
for( ; rnt; -- rnt) q.push(tmp[rnt]);
sum += cnt[i];//获取下一个位置(已处理总数)
// check();
}
while(!q.empty()) ans -= q.top(), q.pop();
printf("%lld\n", ans);
} int main()
{
freopen("in.in", "r", stdin);
pre();
work();
fclose(stdin);
return 0;
}

CF335F Buy One, Get One Free 贪心的更多相关文章

  1. 【CF865D】Buy Low Sell High(贪心)

    [CF865D]Buy Low Sell High(贪心) 题面 洛谷 CF 题解 首先有一个\(O(n^2)\)的\(dp\)很显然,设\(f[i][j]\)表示前\(i\)天手中还有\(j\)股股 ...

  2. LEETCODE —— Best Time to Buy and Sell Stock II [贪心算法]

    Best Time to Buy and Sell Stock II Say you have an array for which the ith element is the price of a ...

  3. hdu3438 Buy and Resell(优先队列+贪心)

    Buy and Resell Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)To ...

  4. CF867E: Buy Low Sell High(贪心, STL) (hdu6438)

    Description 有nn个城市,第ii个城市商品价格为aiai​,从11城市出发依次经过这nn个城市到达n n城市,在每个城市可以把手头商品出售也可以至多买一个商品,问最大收益. Input 第 ...

  5. CodeForces - 867E Buy Low Sell High (贪心 +小顶堆)

    https://vjudge.net/problem/CodeForces-867E 题意 一个物品在n天内有n种价格,每天仅能进行买入或卖出或不作为一种操作,可以同时拥有多种物品,问交易后的最大利益 ...

  6. [LeetCode] Best Time to Buy and Sell Stock II 贪心算法

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  7. Leetcode 122 Best Time to Buy and Sell Stock II 贪心

    用一个数组表示股票每天的价格,数组的第i个数表示股票在第i天的价格.交易次数不限,但一次只能交易一支股票,也就是说手上最多只能持有一支股票,求最大收益. 关键:能赚就赚 class Solution ...

  8. 【leetcode刷题笔记】Best Time to Buy and Sell Stock II

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  9. LeetCode--Best Time to Buy and Sell Stock (贪心策略 or 动态规划)

    Best Time to Buy and Sell Stock Total Accepted: 14044 Total Submissions: 45572My Submissions Say you ...

随机推荐

  1. Maven学习(一)-----Maven安装配置总结

    想要安装 Apache Maven 在Windows 系统上, 需要下载 Maven 的 zip 文件,并将其解压到你想安装的目录,并配置 Windows 环境变量. 所需工具 : JDK 1.8 M ...

  2. JVM知识(上)

    目录 什么是JVM? JVM的生命周期 JVM的体系结构 JVM的数据类型 java虚拟机被称为"虚拟",因为它是一个抽象的计算机定义的规范.要运行一个Java程序,需要一个抽象的 ...

  3. katalon系列九:DEBUG调试功能

    Katalon Studio做为一个IDE,具有和其他IDE一样的Debug功能,可以让我们方便的调试代码.将脚本切换到Script模式,在你想设断点的行首双击,或右击选择:(Groovy)Toggl ...

  4. Vue学习计划基础笔记(六) - 组件基础

    组件基础 目标: 掌握组件的构建方式 掌握如何复用组件.父子组件如何传值.如何向父组件发送消息 掌握如何通过插槽分发内容 了解解析dom模板时的注意事项 了解动态组件 组件 组件理解起来大概上就和ph ...

  5. 概念这种东西--node.js

    概念是一个既简单又复杂.既招人爱又招人恨的东西.概念是对一事务或现象的抽象.抽象好了,那就太方便问题的解决了,抽象坏了,那就驴唇不对马嘴,反而会让逻辑一塌糊涂.现实中经常有这样的概念:东北人怎么怎么样 ...

  6. TW实习日记:第六天

    今日的一整天都是在开发微信相关的接口,因为项目的系统是嵌在企业微信中,所以不可避免的要产生微信UserID和企业系统ID的匹配关系,那么就需要用手机号或是邮箱这种两边都存在的唯一参数进行匹配.然后再将 ...

  7. SQL Server临时表漫谈

    SQL Server是微软的关系型数据库,对于刚入门的我是一个非常友好的开发工具.可视化界面的安装与操作,非常适合刚入门的我. 其实大家要找这方面的资料,在网上一搜一大堆,这里我就不赘述那些了,基本都 ...

  8. JAVA学习笔记--组合与继承

    JAVA一个很重要的功能就是代码的可复用性,代码复用可以大大提升编程效率.这里主要介绍两种代码复用方式:组合和继承. 一.组合 组合比较直观,只需在新的类中产生现有类的对象,新的类由现有类的对象组成, ...

  9. Python学习小目录汇总

    python其他知识目录 python基础知识-1 1.typora软件使用 2.python解释器安装 3.Python解释器环境变量添加 4.计算机编码知识: 5.输出print(): 6.变量 ...

  10. Django 使用 Celery 实现异步任务

    对于网站来说,给用户一个较好的体验是很重要的事情,其中最重要的指标就是网站的浏览速度.因此服务端要从各个方面对网站性能进行优化,比如可采用CDN加载一些公共静态文件,如js和css:合并css或者js ...