1. 程式人生 > >【BZOJ4946】[NOI2017]蔬菜(貪心)

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

!= 假設 復雜度 限制 既然 opera 不知道 https 註意

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

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