【BZOJ4946】[NOI2017]蔬菜(贪心)

题面

BZOJ

洛谷

UOJ

题解

忽然发现今年\(NOI\)之前的时候切往年\(NOI\)的题目,就\(2017\)年的根本不知道怎么下手(一定是我太菜了)

这题是一道神仙题(下定义),然而部分分多得不得了,不知道写一个费用流可以得多少分。

我决定先强行插入一下费用流的做法,费用流是这样子的:首先对于蔬菜拆点,每一天拆出一个点,因为蔬菜可以购买的量逐渐递减,因此每一天向下一天连接流量为当前天减少\(d\)的边,费用为\(0\),然后考虑把蔬菜卖出去,那么就是从一个蔬菜拆出来的某一天向汇点连边,因为限制每天购买的总量,所以再对于每一天的购买的蔬菜拆一个点,然后这一天的每一个蔬菜向这个点连容量为\(inf\),费用为蔬菜费用的边,再从这个点向汇点连容量为\(m\),费用为\(0\)的边。显然源点向每个蔬菜的第一天连容量为蔬菜数量,费用为\(0\)的边,至于第一次购买产生的额外贡献,我们把连的那条边拆出一个单位来,再额外链接一下容量为\(1\),费用为第一次购买产生的额外贡献的边。这样子连边就好了(应该是对的)。

跳出来,往正解的方面想。

显然不难发现一个\(O(nQ)\)的贪心,蔬菜会逐渐减少很不好做,我们倒过来,反过来考虑每一天,那么蔬菜的数量变成了每一天都增加每种蔬菜一定量,然后我们需要倒着买蔬菜就好了。可能需要数据结构什么的维护一下,但是大致的复杂度就是上述的东西。

我们现在再正着考虑,假设我们知道我们在\(p\)天的时候的最优解中,买了哪些蔬菜,那么我们可以很容易的得到\(p-1\)天的答案,显然只需要把利润最小的那\(m\)个蔬菜给去掉就好了,因为第\(p-1\)天可以购买的蔬菜不会少于第\(p\)天,在第\(p\)天能够买到的,在\(p-1\)天也一定能够买到。

前面说的不是很清楚,现在考虑如何求解第\(p\)天的答案。我们既然是增加蔬菜,那么这个操作很容易维护,只需要搞一个堆出来,然后每次把当前所拥有的所有蔬菜全部拿出来贪心取就好了,稍微注意一下第一次选产生的额外贡献的细节就好。然后考虑如何递推回去,还是拿一个堆维护,同理注意一下第一次选产生的额外贡献。

既然这么讲了贪心怎么写,是不是觉得其实这就是一个模拟费用流的过程啊,倒推回去就是一个退流的过程,正推的贪心,显然每天只有那么几条路径,用堆维护等价于跑费用流,忽然感觉很妙啊。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 100100
#define pb push_back
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Node{int v,i;}S[MAX];
bool operator<(Node a,Node b){return a.v<b.v;}
int P=1e5,top,sum;
int n,m,Qry,a[MAX],s[MAX],c[MAX],x[MAX],used[MAX];
ll ans[MAX];
bool vis[MAX];
vector<int> d[MAX];
priority_queue<Node> Q;
int main()
{
n=read();m=read();Qry=read();
for(int i=1;i<=n;++i)a[i]=read(),s[i]=read(),c[i]=read(),x[i]=read();
for(int i=1;i<=n;++i)
if(!x[i])d[P].pb(i);
else d[min(P,(c[i]+x[i]-1)/x[i])].pb(i);
for(int i=P;i;--i)
{
for(int j=0,l=d[i].size();j<l;++j)
Q.push((Node){a[d[i][j]]+s[d[i][j]],d[i][j]});
if(Q.empty())continue;
for(int j=m;j&&!Q.empty();)
{
Node u=Q.top();Q.pop();
if(!vis[u.i])
{
vis[u.i]=true;ans[P]+=u.v;used[u.i]+=1;--j;
if(c[u.i]>1)Q.push((Node){a[u.i],u.i});
}
else
{
int rest=min(j,c[u.i]-used[u.i]-(i-1)*x[u.i]);
ans[P]+=1ll*rest*u.v;used[u.i]+=rest;j-=rest;
if(used[u.i]!=c[u.i])S[++top]=(Node){a[u.i],u.i};
}
}
while(top)Q.push(S[top--]);
}
while(!Q.empty())Q.pop();
for(int i=1;i<=n;++i)sum+=used[i];
for(int i=1;i<=n;++i)
if(used[i]==1)Q.push((Node){-s[i]-a[i],i});
else if(used[i])Q.push((Node){-a[i],i});
for(int i=P-1;i;--i)
{
ans[i]=ans[i+1];
while(sum>i*m&&!Q.empty())
{
Node u=Q.top();Q.pop();u.v*=-1;
if(used[u.i]>1)
{
int rest=min(sum-i*m,used[u.i]-1);
used[u.i]-=rest;sum-=rest;ans[i]-=1ll*rest*u.v;
if(used[u.i]==1)Q.push((Node){-a[u.i]-s[u.i],u.i});
else Q.push((Node){-a[u.i],u.i});
}
else --sum,--used[u.i],ans[i]-=u.v;
}
}
while(Qry--)printf("%lld\n",ans[read()]);
return 0;
}

最新文章

  1. Replication的犄角旮旯(五)--关于复制identity列
  2. C++变量命名规则
  3. Cxgrid获取选中行列,排序规则,当前正在编辑的单元格内的值
  4. 【bzoj1798】维护序列
  5. JAVA-面向对象-多态
  6. 【性能诊断】六、并发场景的性能分析(windbg案例,大量的内部异常造成CPU飙升)
  7. ADO.NET+Access: 3,参数 @departmentName 没有默认值
  8. Mac下kernel_task占用大量CPU怎么办?
  9. Django web 开发指南 no such table:
  10. OCR技术浅探:基于深度学习和语言模型的印刷文字OCR系统
  11. 【java】java反射初探 ——“当类也学会照镜子”
  12. 14 Zabbix Item类型之Simple checks类型
  13. 使用Maven对JAVA程序打包-带主类、带依赖【转】
  14. 10W年薪和30W+年薪的产品经理差距在哪?
  15. Newtonsoft.Json 序列化器的重写
  16. 理解Linux系统中的load average(图文版)
  17. winform版本自动更新
  18. Linux GNU C
  19. Dreamweaver安装与破解
  20. python中的binascii模块

热门文章

  1. Configure Many-to-Many relationship:
  2. 用HTML,Vue+element-UI做桌面UI
  3. 秒懂AOP
  4. JQuery的事件委托;jQuery注册事件;jQuery事件解绑
  5. sql server: 最短路径
  6. csharp: sum columns or rows in a dataTable
  7. JS之用ES6 Promise解决回调地狱(这里以小程序为例)
  8. C++解析 xml,用到pugixml库
  9. 06:合法 C 标识符
  10. 【代码笔记】Web-ionic单选框