一道思路很好的题,因为篇幅太长赶时间,以下多数转自这里
【题目】
定义了一种蔬菜为:
,有
种蔬菜
意思是蔬菜的价格为
,第一份卖出时价格为
,一共有
份,每天会有
份过期;每天最多卖出
份蔬菜,多组输入天数
依次最大化收入。
【解题思路】
首先有一个费用流的做法是这样子的:首先对于蔬菜拆点,每一天拆出一个点,因为蔬菜可以购买的量逐渐递减,因此每一天向下一天连接流量为当前天减少
的边,费用为
,然后考虑把蔬菜卖出去,那么就是从一个蔬菜拆出来的某一天向汇点连边,因为限制每天购买的总量,所以再对于每一天的购买的蔬菜拆一个点,然后这一天的每一个蔬菜向这个点连容量为
,费用为蔬菜费用的边,再从这个点向汇点连容量为
,费用
的边。显然源点向每个蔬菜的第一天连容量为蔬菜数量,费用为
的边,至于第一次购买产生的额外贡献,我们把连的那条边拆出一个单位来,再额外链接一下容量为
,费用为第一次购买产生的额外贡献的边。这样子连边就好了。
跳出来,往正解的方面想。
显然不难发现一个
的贪心,蔬菜会逐渐减少很不好做,我们倒过来,反过来考虑每一天,那么蔬菜的数量变成了每一天都增加每种蔬菜一定量,然后我们需要倒着买蔬菜就好了。可能需要数据结构什么的维护一下,但是大致的复杂度就是上述的东西。
我们现在再正着考虑,假设我们知道我们在 天的时候的最优解中,买了哪些蔬菜,那么我们可以很容易的得到 天的答案,显然只需要把利润最小的那 个蔬菜给去掉就好了,因为第 天可以购买的蔬菜不会少于第 天,在第 天能够买到的,在 天也一定能够买到。
前面说的不是很清楚,现在考虑如何求解第 天的答案。我们既然是增加蔬菜,那么这个操作很容易维护,只需要搞一个堆出来,然后每次把当前所拥有的所有蔬菜全部拿出来贪心取就好了,稍微注意一下第一次选产生的额外贡献的细节就好。然后考虑如何递推回去,还是拿一个堆维护,同理注意一下第一次选产生的额外贡献。
既然这么讲了贪心怎么写,是不是觉得其实这就是一个模拟费用流的过程啊,倒推回去就是一个退流的过程,正推的贪心,显然每天只有那么几条路径,用堆维护等价于跑费用流。
复杂度
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+10,P=1e5;
int n,m,Q,sum;
int used[N],vis[N];
ll ans[N];
vector<int>d[N];
stack<pii>s;
priority_queue<pii>q;
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
struct data
{
int a,s,c,x;
void rd(){a=read();s=read();c=read();x=read();}
}a[N];
int main()
{
#ifndef ONLINE_JUDGE
freopen("BZOJ4946.in","r",stdin);
freopen("BZOJ4946.out","w",stdout);
#endif
n=read();m=read();Q=read();
for(int i=1;i<=n;++i) a[i].rd();
for(int i=1;i<=n;++i)
if(!a[i].x) d[P].pb(i); else d[min(P,(a[i].c+a[i].x-1)/a[i].x)].pb(i);
for(int i=P;i;--i)
{
for(int j=0,id;j<(int)d[i].size();++j)
id=d[i][j],q.push(mkp(a[id].a+a[id].s,id));
for(int j=m;j && !q.empty();)
{
int w=q.top().fi,v=q.top().se;q.pop();
if(!vis[v])
{
vis[v]=1;ans[P]+=w;++used[v];--j;
if(a[v].c>1) q.push(mkp(a[v].a,v));
}
else
{
int res=min(j,a[v].c-used[v]-(i-1)*a[v].x);
ans[P]+=(ll)res*w;used[v]+=res;j-=res;
if(used[v]^a[v].c) s.push(mkp(a[v].a,v));
}
}
while(!s.empty()) q.push(s.top()),s.pop();
}
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(mkp(-a[i].s-a[i].a,i));
else if(used[i]) q.push(mkp(-a[i].a,i));
for(int i=P-1;i;--i)
{
ans[i]=ans[i+1];
while(sum>i*m && !q.empty())
{
int w=-q.top().fi,v=q.top().se;q.pop();
if(used[v]>1)
{
int res=min(sum-i*m,used[v]-1);
used[v]-=res;sum-=res;ans[i]-=(ll)res*w;
if(used[v]==1) q.push(mkp(-a[v].a-a[v].s,v));
else q.push(mkp(-a[v].a,v));
}
else --sum,--used[v],ans[i]-=w;
}
}
while(Q--) printf("%lld\n",ans[read()]);
return 0;
}
【总结】
niubi