外卖(food) & 洛谷4040宅男计划 三分套二分&贪心
【题目描述】
叫外卖是一个技术活,宅男宅女们一直面对着一个很大的矛盾,如何以有限的金钱在宿舍宅得尽量久。
外卖店一共有 N 种食物,每种食物有固定的价钱 Pi 与保质期 Si ,保质期的意义是食物会在被买的第 Si 天后过期。譬如你在今天叫了一个 Si =1 的食物,则你必须在今天或者明天吃掉它。
现在你有 M 元钱,每次叫外卖需要 F 元的运费,送外卖的小哥身强体壮,叫一次可以帮你带来任意份任意种食物,请问在保证每天都吃到至少一份未过期食物的前提下,你最多能宅多久?
【输入文件】
第一行 T 表示数据组数
对于每组数据,第一行三个整数 M F N
接下来 N 行每行两个整数 P i S i
【输出文件】
对于每组数据,在一行中输出一个数表示最多宅的天数
【样例输入】
food.in
332 5 2
5 0
10 2
10 10 1
10 10
10 1 1
1 5
【样例输出】
food.out
3
0
8
【数据约定】
30%:0 <= Si <= 10,1 <= M <= 20,1 <= N <= 10
100%:0 ≤ S i ≤ 2000000,1 ≤ M ≤ 2000000
100%:T ≤ 10,1 ≤ F ≤ M,1 ≤ N ≤ 200,1 ≤ P i ≤ M
思路:
觉得这题好难呀 也不知道怎么想到正解 就直接讲讲正解吧
solution说 :“这是道构造贪心的好题”
发现这题无从下手,但是如果知道点外卖点了多少次就好做了(我并没有觉得很好做???)
所以我们枚举点外卖的次数 这样我们就知道总的运费了 也就知道了买食物的总钱数
然后我们需要考虑的是两次点外卖之间的间隔 这里是一个贪心
首先我们初始化的时候就要去掉那些又贵保质期又短的食物 保证食物的保质期与价格正相关
先说结论吧 当点外卖的两两之间的间隔时间相等时 可以维持天数是最多的
假如我们要维持6天 点2次外卖 那么在第一天和第四天点外卖花的钱是最少的
因为这样子的话点的外卖中保质期最长的只需要2天就可以了 而价钱与保质期正相关
保质期短 价钱就少
我们二分每轮点外卖维持的天数
可以预处理出点一次外卖维持一定天数的最小价钱
所以这里就比较好判断了
但是要注意的是 可能点一定次数的外卖并且维持一定的天数后 还有钱剩余
这时就可以在最后一次点外卖的时候多点一些 维持更多的天数
CODE:
#include<iostream>
#include<cstdio>
#include<cstring>
#define go(i,a,b) for(register int i=a;i<=b;i++)
#define yes(i,a,b) for(register int i=a;i>=b;i--)
#define ll long long
#define M 200+10
#define N 1000000+10
#define inf 21000000
using namespace std;
ll read()
{
int x=,y=;char c=getchar();
while(c<''||c>'') {if(c=='-') y=-;c=getchar();}
while(c>=''&&c<='') {x=(x<<)+(x<<)+c-'';c=getchar();}
return x*y;
}
ll T,n,m,f,maxn,ans,p[M],s[M],minn[N],sm[N];
void work(ll st,ll my) //step money
{
ll l=,r=maxn;
while(l<r)
{
ll mid=(l+r+)>>;
if(sm[mid]*st<=my) l=mid;
else r=mid-;
}
ans=max(ans,l*st+(my-sm[l]*st)/minn[l+]);
}
int main()
{
T=read();
while(T--)
{
m=read();f=read();n=read();
memset(minn,,sizeof(minn));
maxn=;ans=;
go(i,,n)
{
p[i]=read();s[i]=read()+;
minn[s[i]]=min(minn[s[i]],p[i]);
maxn=max(maxn,s[i]);
}
yes(i,maxn-,) minn[i]=min(minn[i],minn[i+]);
go(i,,maxn) sm[i]=sm[i-]+minn[i];
if(!f) {printf("%lld\n",m/minn[]);continue ;}
go(i,,m/f) work(i,m-i*f);
printf("%lld\n",ans);
}
}
和上面那题唯一不同的地方就是 数据范围
这里的数据范围是:0<=Si<=10^18,1<=F,Pi,M<=10^18,1<=N<=200
于是用上面的代码就会超时啦
然后发现点外卖的次数是可以三分的(当然不是我发现的)
以下摘自gql's blog:
可以证明,外卖的轮次与存活天数是个二次函数关系
来下面我再来证明下Qw
首先感性理解下,假如我现在有很多钱,我只让快递小哥来一次,我最多只能活保质期max天嘛
然后如果小哥来两次,我依然付得起很多很多东西,我就能活2*保质期max对趴
以此类推我们可以发现最初我的钱很多的时候我能活的天数是增加的
但是!再举个栗子
假如我一天点一次外卖,我就会要花很多很多外卖费,活的天数可能更少
通过这个极端的栗子可以发现,如果我点外卖点的太频繁,我花的外卖费就很多,能用来买东西的钱就很少辣
所以它是个单峰函数!
有点感性,,,?
那那那讲下另一个方法,lzq还港了一个方法,感觉似懂非懂的我大概说下QAQ
就,我把花费分成两部分,一部分是快递费一部分是买东西
快递费就分摊到了每一天嘛
然后我们现在相当于是说要每一天的平均花费min
那如果我这一轮买的东西多一个
我的快递费平摊的对象就+1,同时的我买东西的花费也会增加
就是从f/i+P/i变成了f/(i+1)+(P+p[i])/(i+1)
利用反比例函数的性质可以发现开始的时候f/i这个变化值是会很大的,而相对而言的p增加的少一些,所以减少比增加多,它就会是单调增的
但是到了后期f/i的变化量就很小了嘛,还有一点就是到了后期的pi会很大(因为我们已经是让p单调增的了
所以增加的就比减少的多了,所以就成了单调减的
综上,它是个单调函数
但是并不仅仅只要三分那么简单 由于这题的 si 实在是太大了
开不了那么大的minn[]和sm[] 所以我们就不能向之前那样子预处理了
每次都要自己计算qwq
优秀题解CODE:
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
int n;int cnt;ll m;ll f;
struct food
{
ll cst;ll kep;
friend bool operator <(food a,food b){return a.kep<b.kep;}
}tp[],fo[];
inline ll cost(ll t)//计算存活到t的花费
{
ll res=;
for(int i=;i<=cnt;i++)
{
if(res<){return -;}
if(t>fo[i].kep){res+=fo[i].cst*fo[i].kep;t-=fo[i].kep;}
else {res+=fo[i].cst*t;return res;}
}return -;//这里是避免爆INF
}
inline ll calcd(ll pm)//计算花多少钱可以存活多少天
{
ll l=;ll r=pm;
while(l<r)//单调可以二分
{
ll mid=(l+r+)/;ll y=cost(mid);
if(y==-||y>pm){r=mid-;}else {l=mid;}
}
return r;
}
inline ll calct(ll r)//计算T
{
ll rest=m-r*f;if(rest<||rest>m){return ;}//还是避免爆longlong
ll pm=rest/r;ll k=calcd(pm);if(k==){return ;}
ll c2=cost(k+);ll c1=cost(k);if(c2==-){return r*k;}
else {rest-=c1*r;return rest/(c2-c1)+r*k;}
}
int main()
{
scanf("%lld%lld%d",&m,&f,&n);
for(int i=;i<=n;i++){scanf("%lld%lld",&tp[i].cst,&tp[i].kep);}
sort(tp+,tp+n+);tp[].kep=-;fo[].kep=-;
for(int i=;i<=n;i++)//单调栈去掉无用的东西
{while(cnt!=&&tp[i].cst<fo[cnt].cst){cnt--;}fo[++cnt]=tp[i];}
for(int i=cnt;i>=;i--){fo[i].kep-=fo[i-].kep;}//后向差分方便计算
ll l=;ll r=m/f+;//三分法求函数最值
while(r-l>)//缩小区间
{
ll x1=l+(r-l)/;ll x2=l+((r-l)*)/;
ll y1=calct(x1);ll y2=calct(x2);
if(y1<y2){l=x1;}else {r=x2;}
}
ll res=calct(l);//然后暴力枚举最大值
for(ll i=l+;i<=r;i++){res=max(res,calct(i));}
printf("%lld",res);return ;//拜拜程序~
}
外卖(food) & 洛谷4040宅男计划 三分套二分&贪心的更多相关文章
- Bzoj 3874: [Ahoi2014&Jsoi2014]宅男计划 三分+贪心
3874: [Ahoi2014&Jsoi2014]宅男计划 Time Limit: 1 Sec Memory Limit: 256 MBSubmit: 861 Solved: 336[Su ...
- 洛谷$P4040\ [AHOI2014/JSOI2014]$宅男计划 贪心
正解:三分+贪心 解题报告: 传送门$QwQ$ 其实很久以前的寒假就考过了,,,但那时候$gql$没有好好落实,就只写了个二分,并没有二分套三分,就只拿到了$70pts$ #include <b ...
- 【BZOJ3874】[AHOI&JSOI2014]宅男计划(贪心,三分)
[BZOJ3874][AHOI&JSOI2014]宅男计划(贪心,三分) 题面 BZOJ 洛谷 题解 大力猜想一最长的天数和购买外卖的总次数是单峰的.感性理解一下就是买\(0\)次是\(0\) ...
- bzoj3874&2832 [Ahoi2014]宅男计划 模拟退火,三分
[Ahoi2014&Jsoi2014]宅男计划 Time Limit: 1 Sec Memory Limit: 256 MBSubmit: 962 Solved: 371[Submit][ ...
- [luogu] P4040 [AHOI2014/JSOI2014]宅男计划(贪心)
P4040 [AHOI2014/JSOI2014]宅男计划 题目背景 自从迷上了拼图,JYY就变成了个彻底的宅男.为了解决温饱问题,JYY不得不依靠叫外卖来维持生计. 题目描述 外卖店一共有N种食物, ...
- 洛谷 P2762 太空飞行计划问题 P3410 拍照【最大权闭合子图】题解+代码
洛谷 P2762 太空飞行计划问题 P3410 拍照[最大权闭合子图]题解+代码 最大权闭合子图 定义: 如果对于一个点集合,其中任何一个点都不能到达此集合以外的点,这就叫做闭合子图.每个点都有一个权 ...
- 洛谷 P1114 “非常男女”计划
To 洛谷.1114 “非常男女”计划 题目描述 近来,初一年的XXX小朋友致力于研究班上同学的配对问题(别想太多,仅是舞伴),通过各种推理和实验,他掌握了大量的实战经验.例如,据他观察,身高相近的人 ...
- 「AHOI2014/JSOI2014」宅男计划
「AHOI2014/JSOI2014」宅男计划 传送门 我们首先要发现一个性质:存货天数随买食物的次数的变化类似于单峰函数. 具体证明不会啊,好像是二分加三分来证明?但是没有找到明确的严格证明. 感性 ...
- 【洛谷5008】逛庭院(Tarjan,贪心)
[洛谷5008]逛庭院(Tarjan,贪心) 题面 洛谷 题解 如果图是一个\(DAG\),我们可以任意选择若干个不是入度为\(0\)的点,然后把它们按照拓扑序倒序删掉,不难证明这样一定是合法的. 现 ...
随机推荐
- Idea(三)常用插件以及快捷键总结
idea常用插件以及快捷键 现在开发中和日常自己开发都统一换成了idea进行开发了.现在针对自己常用到的插件和快捷键进行总结记录下. 插件 Alibaba Java Coding Guidelines ...
- Shopping Bands Rank & SBR
Shopping Bands Rank SBR https://www.guiderank.org/index.html Nike Air Zoom Pegasus 34 http://www.shi ...
- js时间戳转换日期格式和日期计算
一.时间戳转换日期 function formatDate(datetime) { // 获取年月日时分秒值 slice(-2)过滤掉大于10日期前面的0 var year = datetime.ge ...
- Dapper 事务处理
例子: using (var connection = GetOpenConnection()) using (var transaction = connection.BeginTransactio ...
- HDU1565_方格取数(1)
给一个数字方阵,你要从中间取出一些数字,保证相邻的两个数字不同时被取出来,求取出来的最大的和是多少? 建立图模型,对于行列的和为奇数的格子,建立一条从原点到达这个点的边,对于行列和为偶数的格子,建立一 ...
- 队列java实现
队列是一种线性数据结构,是一种运算受限的线性表,只允许在队尾插入,在队头删除.运算规则是先进先出.恰好和栈相反.栈是先进后出.因为栈只在栈顶做删除和插入. 队列按照存储结构可以分为顺序队列和链式队列. ...
- 每日一问(如何在List中加入、设置、获取和删除其中的元素?)
作为集合接口的一部分,对List接口所做的操作,最常见的就是增删查改了.这里总结下JAVA 中List接口及实现该接口的类实现这些操作的方法. 一.增加新的元素的方法 在Collection接口中定义 ...
- BZOJ3162 独钓寒江雪(哈希+树形dp)
数独立集显然是可以树形dp的,问题在于本质不同. 假设已经给树确立了一个根并且找到了所有等效(注意是等效而不是同构)子树,那么对转移稍加修改使用隔板法就行了. 关键在于找等效子树.首先将树的重心(若有 ...
- hbase 多个过滤器组合(列表)
使用FilterList要保证过滤器的顺序需要使用List<Filter> private static void mutilFilterData() throws IOException ...
- 【省选水题集Day1】一起来AK水题吧! 题目(更新到B)
题解:http://www.cnblogs.com/ljc20020730/p/6937954.html 水题A: [AHOI2001]质数和分解 题目网址: https://www.luogu.or ...