1. 程式人生 > >【貪心+堆/模擬費用流增廣】BZOJ4946 [NOI2017]蔬菜

【貪心+堆/模擬費用流增廣】BZOJ4946 [NOI2017]蔬菜

一道思路很好的題,因為篇幅太長趕時間,以下多數轉自這裡

【題目】
定義了一種蔬菜為: a i , s i , c

i , x i a_i,s_i,c_i,x_i ,有 n n
種蔬菜
意思是蔬菜的價格為 a i a_i ,第一份賣出時價格為 a i +
s i a_i+s_i
,一共有 c i c_i 份,每天會有 x i x_i 份過期;每天最多賣出 m m 份蔬菜,多組輸入天數 d d 依次最大化收入。
n , m , d , T 1 0 5 n,m,d,T\leq 10^5

【解題思路】
首先有一個費用流的做法是這樣子的:首先對於蔬菜拆點,每一天拆出一個點,因為蔬菜可以購買的量逐漸遞減,因此每一天向下一天連線流量為當前天減少 d d 的邊,費用為 0 0 ,然後考慮把蔬菜賣出去,那麼就是從一個蔬菜拆出來的某一天向匯點連邊,因為限制每天購買的總量,所以再對於每一天的購買的蔬菜拆一個點,然後這一天的每一個蔬菜向這個點連容量為 i n f inf ,費用為蔬菜費用的邊,再從這個點向匯點連容量為 m m ,費用 0 為0 的邊。顯然源點向每個蔬菜的第一天連容量為蔬菜數量,費用為 0 0 的邊,至於第一次購買產生的額外貢獻,我們把連的那條邊拆出一個單位來,再額外連結一下容量為 1 1 ,費用為第一次購買產生的額外貢獻的邊。這樣子連邊就好了。

跳出來,往正解的方面想。
顯然不難發現一個 O ( n Q ) O(nQ) 的貪心,蔬菜會逐漸減少很不好做,我們倒過來,反過來考慮每一天,那麼蔬菜的數量變成了每一天都增加每種蔬菜一定量,然後我們需要倒著買蔬菜就好了。可能需要資料結構什麼的維護一下,但是大致的複雜度就是上述的東西。

我們現在再正著考慮,假設我們知道我們在 p p 天的時候的最優解中,買了哪些蔬菜,那麼我們可以很容易的得到 p 1 p-1 天的答案,顯然只需要把利潤最小的那 m m 個蔬菜給去掉就好了,因為第 p 1 p-1 天可以購買的蔬菜不會少於第 p p 天,在第 p p 天能夠買到的,在 p 1 p-1 天也一定能夠買到。

前面說的不是很清楚,現在考慮如何求解第 p p 天的答案。我們既然是增加蔬菜,那麼這個操作很容易維護,只需要搞一個堆出來,然後每次把當前所擁有的所有蔬菜全部拿出來貪心取就好了,稍微注意一下第一次選產生的額外貢獻的細節就好。然後考慮如何遞推回去,還是拿一個堆維護,同理注意一下第一次選產生的額外貢獻。

既然這麼講了貪心怎麼寫,是不是覺得其實這就是一個模擬費用流的過程啊,倒推回去就是一個退流的過程,正推的貪心,顯然每天只有那麼幾條路徑,用堆維護等價於跑費用流。

複雜度 O ( n l o g n + q m ( l o g   n + l o g   q ) ) O(nlogn +qm(log\ n+log\ q))

【參考程式碼】

#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