P3826 [NOI2017]蔬菜

传送门

注意每一单位蔬菜的变质时间是固定的,不随销售发生变化

固定的......

就是每一个单位的蔬菜在哪一天变质是早就定好了的

发现从第一天推到最后一天很不好搞

考虑反过来,从最后一天推到第一天,这样就相当于每天多一些蔬菜

不管现在怎么卖都不会影响下一天多的蔬菜,不会出现贵的留到后面卖的操作,因为后面蔬菜只会越来越多,不如把空间留给之后可能会出现的更好的菜

可以直接贪心了,每天都卖最贵的 $m$ 单位蔬菜,这样就一定是最优的

然后就是维护每天的各种菜了,开个堆并维护一堆东西,没什么思维...

但是这样还不够,有多组询问

继续倒过来考虑,每天菜只会越来越多,发现 卖 $p$ 天的任意一种操作 卖 $p-1$ 天都能做

唯一的区别只是卖 $p-1$ 天少卖了几个菜

所以可以先处理出 $p=10^5$ 时的答案并把各种操作存一下,然后扔掉价值最小的几种操作直到总操作数在 $p-1$ 天也能做出,就能从 $p$ 推到 $p-1$ 了

然后就可以预处理出所有的询问了

具体维护的时候其实并不容易......看代码注释吧

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2e5+7;
int n,m,K,a[N],s[N],c[N],x[N],sum[N];
//n,m,k,a,s,c,x意义同题面,sum[i]维护当前编号为i的菜卖出的数量
bool fir[N];//判断是否是第一次卖
ll ans[N];//存答案
struct dat1{
    int val,id;//蔬菜的价值,编号
    inline bool operator < (const dat1 &tmp) const {
        return val<tmp.val;
    }
}tmp[17];//队列,存今天卖完的菜编号
priority_queue <dat1> Q1;//维护当前各种存在的蔬菜
struct dat2{
    int val,id;//卖的菜的价值,编号
    inline bool operator < (const dat2 &tmp) const {
        return val>tmp.val;
    }
};
priority_queue <dat2> Q2;//维护当前卖的各种菜
vector <int> V[N];//V[i][j]维护在第i天 第一次出现的 第j种菜的编号
void pre_work()
{
    for(int i=1;i<=n;i++)
    {
        if(x[i]&& c[i]/x[i]+(c[i]%x[i]!=0)<=1e5 )//注意特判
            V[ c[i]/x[i]+(c[i]%x[i]!=0) ].push_back(i);
        else Q1.push((dat1){a[i]+s[i],i});//先加入第一次卖的价值
    }
    for(int now=100000;now>0;now--)//从最后一天考虑
    {
        for(int i=V[now].size()-1;i>=0;i--) Q1.push((dat1){ a[V[now][i]]+s[V[now][i]] , V[now][i] });
        //把每天的新菜加入堆,价值为第一次卖的价值
        int cnt=0;//tmp队列头
        for(int t=0;t<m&&!Q1.empty();)//t记录今天已经卖了多少菜
        {
            dat1 y=Q1.top(); Q1.pop(); int z=y.id;//找出价值最大的菜
            if(fir[z])//不是第一次卖
            {
                int num=min(m-t/*最多能卖的量*/, c[z]-(now-1)*x[z]-sum[z]/*当前此菜的量*/ );
                sum[z]+=num; t+=num; ans[100000]+=1ll*num*y.val;//注意long long
                if(sum[z]!=c[z]) tmp[++cnt]=y;//如果没完全卖完就加入队列(今天的量已经完了)
            }
            else//第一次卖
            {
                sum[z]++; t++; ans[100000]+=y.val; fir[z]=1;//只卖一单位
                if(sum[z]!=c[z]) Q1.push((dat1){a[z],z});//把非第一次卖的价值加入堆
            }
        }
        for(int j=1;j<=cnt;j++) Q1.push(tmp[j]);//下一天了,把今天卖完的菜补充回来
    }
    int tot=0;//记录卖的总数
    for(int i=1;i<=n;i++)//记录各种操作
    {
        if(sum[i]==1) Q2.push((dat2){a[i]+s[i],i});//特判第一次
        else if(sum[i]) Q2.push((dat2){a[i],i});
        tot+=sum[i];
    }
    for(int i=1e5-1;i;i--)//逆推ans
    {
        ans[i]=ans[i+1]; if(tot<=m*i) continue;//卖的数量合法就不用少卖
        for(int t=0;t<tot-m*i&&!Q2.empty();)//t记录当前少卖的数量
        {
            dat2 y=Q2.top(); Q2.pop(); int z=y.id;//把价值最小的操作拿出
            if(sum[z]!=1)//不是第一次
            {
                int num=min(sum[z]-1/*刚好减少到第一次*/,tot-m*i-t/*刚好合法*/);
                sum[z]-=num; t+=num; ans[i]-=1ll*num*y.val;
                if(sum[z]==1) Q2.push((dat2){a[z]+s[z],z});//剩下一单位,把第一次的价值加入
                else Q2.push((dat2){a[z],z});//还不止一单位
            }
            else sum[z]--,t++,ans[i]-=y.val;//第一次卖
        }
        tot=m*i;//更新tot
    }
}
int main()
{
    n=read(),m=read(),K=read();
    for(int i=1;i<=n;i++) a[i]=read(),s[i]=read(),c[i]=read(),x[i]=read();
    pre_work();
    while(K--) printf("%lld\n",ans[read()]);
    return 0;
}

 

posted @ 2019-07-11 14:06  LLTYYC  阅读(223)  评论(0编辑  收藏  举报