[NOI2017] 蔬菜
XXXIX.[NOI2017] 蔬菜
第一眼这个奇奇怪怪的限制,想到网络流。
为了处理这个“每天坏 ”个的限制,我想到的方法是,第一天的 个仅能在第一天销售,就只往代表第一天的点连边;第二天的 个可以在第一天和第二天销售,故往代表第一天和第二天的点连边;第三天就往前三天连边……然后发现这样边数是 的。
尝试消减边数。发现这里面连了很多重复的边,而且如果我们倒着建图,令源点在第 天的位置,汇点在第 天的位置,就能使得边数消减到 ,因为这样子每天的蔬菜作用范围就是一条前缀。
然后我们来思考这张图的实际意义——从后往前推,每天突然出现 单位的菜,并且这之后这些菜都能卖。
稍微思考一下这个方法,就会发现一个非常棒的地方:因为任意时刻,你手头的菜在接下来的时刻也都能卖,所以就不必担心这些菜会烂在手里之类的,直接贪心地挑最贵的菜卖即可。至于那个“第一份菜补贴”的东西,我们就把它想成,某种菜出现的第一天,其中有一颗菜是镀金的,价格高一点,但是一样可以在贪心里算。
但是这个东西的复杂度,算算是 的,必须优化。
然后就不会了
明显,这种优化的唯一可能是从后一天的答案递推到前一天。于是我们考虑先求出第 天(实际上取第 天即可)时的答案。我们对于每天都开一个 vector
。对于每种菜,我们将其编号扔到它全部消失那天的 vector
中。接着,从后往前遍历,用一个堆维护,每当访问到一天时,就将该天 vector
中所有菜,按照补贴价格扔进堆。
接着,从堆弹出 个最贵的菜卖掉。具体而言,如果最贵的是补贴菜,就卖一个补贴菜,然后将平时的价格扔进堆;如果最贵的是平价菜,则目前仓库里剩余的平价菜数量,显然可以通过记录当前已经卖了多少此种菜,与当前天数来推出。推出剩余平价菜数量后,卖出 个菜。如果发现这个菜现有的库存已经卖光了,就把它扔进一个备用数组存着,等今天的销售处理完毕后再把它扔回堆里(因为第二天还有新的菜会到货)。
于是我们现在推出了第 日的答案,也就知道了最优的卖菜方法。我们尝试递推答案。当我们想从 日逆推出 日的答案时,首先先判断手头的菜数量是否不大于 (即前 日所能卖出的菜数),如果是显然就不用放弃某些菜了;否则,从现行的卖菜方法中不断扔出最便宜的菜(可以开一个小根堆维护),直到小根堆的 size
已经达到 。
时间复杂度 。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int LIM=1e5;
int n,m,P,pro[100100],bon[100100],amt[100100],per[100100],sel[100100],tot;
ll res[100100];
vector<int>v[100100],bin;
priority_queue<pair<int,int> >q;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >p;
int main(){
scanf("%d%d%d",&n,&m,&P);
for(int i=1;i<=n;i++){
scanf("%d%d%d%d",&pro[i],&bon[i],&amt[i],&per[i]);
if(!per[i])v[LIM].push_back(i);
else v[min((amt[i]-1)/per[i]+1,LIM)].push_back(i);
}
for(int i=LIM;i;i--){
// printf("%d:\n",i);
for(auto j:v[i])q.push(make_pair(pro[j]+bon[j],j));
int lim=m;
while(lim&&!q.empty()){
int x=q.top().second;q.pop();
// printf("%d,%d\n",x,lim);
if(!sel[x]){lim--,sel[x]++,res[LIM]+=pro[x]+bon[x],q.push(make_pair(pro[x],x));continue;}//first sale
int tmp=min(lim,amt[x]-per[x]*(i-1)-sel[x]);
lim-=tmp,sel[x]+=tmp,res[LIM]+=1ll*tmp*pro[x],bin.push_back(x);
}
while(!bin.empty()){
if(sel[bin.back()]!=amt[bin.back()])q.push(make_pair(pro[bin.back()],bin.back()));
bin.pop_back();
}
tot+=m-lim;
}
for(int i=1;i<=n;i++){
if(!sel[i])continue;
if(sel[i]==1)p.push(make_pair(pro[i]+bon[i],i));
else p.push(make_pair(pro[i],i));
}
// puts("IN");
for(int i=LIM;i;i--){
// printf("%d:\n",i);
res[i-1]=res[i];
if((i-1)*m>=tot)continue;
int lim=tot-(i-1)*m;tot=(i-1)*m;
while(lim&&!p.empty()){
int x=p.top().second;p.pop();
if(sel[x]==1){lim--,sel[x]--,res[i-1]-=pro[x]+bon[x];continue;}
int tmp=min(lim,sel[x]-1);
lim-=tmp,sel[x]-=tmp,res[i-1]-=1ll*tmp*pro[x];
if(sel[x]==1)p.push(make_pair(pro[x]+bon[x],x));
else if(sel[x])p.push(make_pair(pro[x],x));
}
}
for(int i=1,x;i<=P;i++)scanf("%d",&x),printf("%lld\n",res[x]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?