外卖(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\)的点,然后把它们按照拓扑序倒序删掉,不难证明这样一定是合法的. 现 ... 
随机推荐
- ACM数论之旅16---母函数(又名生成函数)(痛并快乐着(╭ ̄3 ̄)╭)
			(前排出售零食瓜子) 前言: 母函数是个很难的东西,难在数学 而ACM中所用的母函数只是母函数的基础 应该说除了不好理解外,其他都是非常简单的 母函数即生成函数,是组合数学中尤其是计数方面的一个重要理 ... 
- HDU1565_方格取数(1)
			给一个数字方阵,你要从中间取出一些数字,保证相邻的两个数字不同时被取出来,求取出来的最大的和是多少? 建立图模型,对于行列的和为奇数的格子,建立一条从原点到达这个点的边,对于行列和为偶数的格子,建立一 ... 
- Python排序算法动态图形化演示(实现代码)
			1.冒泡排序 冒泡排序是最简单也是最容易理解的排序方法,其原理就是重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是 ... 
- java追加写入txt文件
			整理了下网上的资料,数据追加写入txt文件有三种方式,见下面代码: 方法一: public void method1() { FileWriter fw = null; try { //如果文件存在, ... 
- 【uoj131】 NOI2015—品酒大会
			http://uoj.ac/problem/131 (题目链接) 题意 给出一个字符串,每个后缀有一个权值${a_i}$,这些后缀两两之间存在公共前缀.问能够组成长度从0~n-1的公共前缀的后缀的方案 ... 
- 【bug】Could not find method compile() 解决
			集成第三方库出现 Error:Could not find method compile() for arguments [com.android.support:design:23.4.0] on ... 
- [SCOI2016] 背单词 (Trie树)
			$pdf\space solution$ link #include<iostream> #include<algorithm> #include<cstrin ... 
- Jenkins(四)---Jenkins添加密钥对
			一.添加密钥 1.添加git用户和git密码对 ,用于git客户端从gitlab上拉取代码到本地 /** lihaibo 文章内容都是根据自己工作情况实践得出. *版权声明:本博客欢迎转发,但请保留原 ... 
- boost::asio::deadline_timer(理解)
			并发与并行: 并发和并行从宏观上来讲都是同时处理多路请求的概念.但并发和并行又有区别,并行是指两个或者多个事件在同一时刻发生:而并发是指两个或多个事件在同一时间间隔内发生. 1.Timer.1 - 使 ... 
- [转载]hzwer的bzoj题单
			counter: 664BZOJ1601 BZOJ1003 BZOJ1002 BZOJ1192 BZOJ1303 BZOJ1270 BZOJ3039 BZOJ1191 BZOJ1059 BZOJ120 ... 
