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


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM