【貪心+堆/模擬費用流增廣】BZOJ4946 [NOI2017]蔬菜
一道思路很好的題,因為篇幅太長趕時間,以下多數轉自這裡
【題目】
定義了一種蔬菜為:
,有
種蔬菜
意思是蔬菜的價格為
,第一份賣出時價格為
,一共有
份,每天會有
份過期;每天最多賣出
份蔬菜,多組輸入天數
依次最大化收入。
【解題思路】
首先有一個費用流的做法是這樣子的:首先對於蔬菜拆點,每一天拆出一個點,因為蔬菜可以購買的量逐漸遞減,因此每一天向下一天連線流量為當前天減少
的邊,費用為
,然後考慮把蔬菜賣出去,那麼就是從一個蔬菜拆出來的某一天向匯點連邊,因為限制每天購買的總量,所以再對於每一天的購買的蔬菜拆一個點,然後這一天的每一個蔬菜向這個點連容量為
,費用為蔬菜費用的邊,再從這個點向匯點連容量為
,費用
的邊。顯然源點向每個蔬菜的第一天連容量為蔬菜數量,費用為
的邊,至於第一次購買產生的額外貢獻,我們把連的那條邊拆出一個單位來,再額外連結一下容量為
,費用為第一次購買產生的額外貢獻的邊。這樣子連邊就好了。
跳出來,往正解的方面想。
顯然不難發現一個
的貪心,蔬菜會逐漸減少很不好做,我們倒過來,反過來考慮每一天,那麼蔬菜的數量變成了每一天都增加每種蔬菜一定量,然後我們需要倒著買蔬菜就好了。可能需要資料結構什麼的維護一下,但是大致的複雜度就是上述的東西。
我們現在再正著考慮,假設我們知道我們在 天的時候的最優解中,買了哪些蔬菜,那麼我們可以很容易的得到 天的答案,顯然只需要把利潤最小的那 個蔬菜給去掉就好了,因為第 天可以購買的蔬菜不會少於第 天,在第 天能夠買到的,在 天也一定能夠買到。
前面說的不是很清楚,現在考慮如何求解第 天的答案。我們既然是增加蔬菜,那麼這個操作很容易維護,只需要搞一個堆出來,然後每次把當前所擁有的所有蔬菜全部拿出來貪心取就好了,稍微注意一下第一次選產生的額外貢獻的細節就好。然後考慮如何遞推回去,還是拿一個堆維護,同理注意一下第一次選產生的額外貢獻。
既然這麼講了貪心怎麼寫,是不是覺得其實這就是一個模擬費用流的過程啊,倒推回去就是一個退流的過程,正推的貪心,顯然每天只有那麼幾條路徑,用堆維護等價於跑費用流。
複雜度
【參考程式碼】
#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