[NOI2017] 蔬菜

XXXIX.[NOI2017] 蔬菜

第一眼这个奇奇怪怪的限制,想到网络流。

为了处理这个“每天坏 \(c_i\)”个的限制,我想到的方法是,第一天的 \(c_i\) 个仅能在第一天销售,就只往代表第一天的点连边;第二天的 \(c_i\) 个可以在第一天和第二天销售,故往代表第一天和第二天的点连边;第三天就往前三天连边……然后发现这样边数是 \(O(n^2)\) 的。

尝试消减边数。发现这里面连了很多重复的边,而且如果我们倒着建图,令源点在第 \(\inf\) 天的位置,汇点在第 \(0\) 天的位置,就能使得边数消减到 \(O(n)\),因为这样子每天的蔬菜作用范围就是一条前缀。

然后我们来思考这张图的实际意义——从后往前推,每天突然出现 \(c_i\) 单位的菜,并且这之后这些菜都能卖。

稍微思考一下这个方法,就会发现一个非常棒的地方:因为任意时刻,你手头的菜在接下来的时刻也都能卖,所以就不必担心这些菜会烂在手里之类的,直接贪心地挑最贵的菜卖即可。至于那个“第一份菜补贴”的东西,我们就把它想成,某种菜出现的第一天,其中有一颗菜是镀金的,价格高一点,但是一样可以在贪心里算。

但是这个东西的复杂度,算算是 \(O(n^2m\log n)\) 的,必须优化。

然后就不会了

明显,这种优化的唯一可能是从后一天的答案递推到前一天。于是我们考虑先求出第 \(\inf\) 天(实际上取第 \(10^5\) 天即可)时的答案。我们对于每天都开一个 vector。对于每种菜,我们将其编号扔到它全部消失那天的 vector 中。接着,从后往前遍历,用一个堆维护,每当访问到一天时,就将该天 vector 中所有菜,按照补贴价格扔进堆。

接着,从堆弹出 \(m\) 个最贵的菜卖掉。具体而言,如果最贵的是补贴菜,就卖一个补贴菜,然后将平时的价格扔进堆;如果最贵的是平价菜,则目前仓库里剩余的平价菜数量,显然可以通过记录当前已经卖了多少此种菜,与当前天数来推出。推出剩余平价菜数量后,卖出 \(\min(\text{此种菜数量},\text{本日剩余卖菜名额})\) 个菜。如果发现这个菜现有的库存已经卖光了,就把它扔进一个备用数组存着,等今天的销售处理完毕后再把它扔回堆里(因为第二天还有新的菜会到货)。

于是我们现在推出了第 \(\inf\) 日的答案,也就知道了最优的卖菜方法。我们尝试递推答案。当我们想从 \(i+1\) 日逆推出 \(i\) 日的答案时,首先先判断手头的菜数量是否不大于 \(im\)(即前 \(i\) 日所能卖出的菜数),如果是显然就不用放弃某些菜了;否则,从现行的卖菜方法中不断扔出最便宜的菜(可以开一个小根堆维护),直到小根堆的 size 已经达到 \(im\)

时间复杂度 \(O(nm\log n)\)

代码:

#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;
}

原文地址:https://www.cnblogs.com/Troverld/p/14612762.html