LibreOJ #2306.「NOI2017」蔬菜 贪心+堆+并查集

30 篇文章 0 订阅
18 篇文章 0 订阅

题意

好长懒得写。

分析

先考虑如果只有一组询问的话要怎么做。
不难想到我们可以反过来贪心,这样删物品就变成了加物品,只要用一个堆来维护下就好了。
现在有多组询问的话,先找到最大的询问mx,然后做一次贪心得到第mx天的答案。
因为若某件物品在第t天一定能买,则其在第t天之前也一定能买。那么如果我们知道第t天的答案,可以通过把第t天的物品贪心地填进前t-1天来得到第t-1天的答案。
那么只要用并查集维护一下哪些时间还没被填完,如果有的话就直接填进去,否则就在前面的所有蔬菜里面替换掉价值最小的。
实现起来细节比较多。
时间复杂度 O(nmlogn) O ( n m l o g n )

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#define mp(x,y) std::make_pair(x,y)

typedef long long LL;
typedef std::pair<int,int> pi;

const int N=100005;

int n,m,k,val[N],s[N],c[N],x[N],q[N],a[N],f[N],ob[N],da[N];
LL ans[N];
std::priority_queue<pi> que;
std::vector<int> beg[N];
struct Queue
{
    std::priority_queue<pi> a,b;

    void work() {while (!b.empty()&&a.top()==b.top()) a.pop(),b.pop();}
    void push(pi x) {a.push(x);}
    void erase(pi x) {b.push(x);}
    pi top() {work();return a.top();}
    bool empty() {work();return a.empty();}
    void pop() {work();a.pop();}
}Q,tmp[N];


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*10+ch-'0';ch=getchar();}
    return x*f;
}

int find(int x)
{
    if (f[x]==x) return x;
    else return f[x]=find(f[x]);
}

int get_sum(int id,int y)
{
    if (!x[id]) return 0;
    else return x[id]*y;
}

int main()
{
    n=read();m=read();k=read();
    for (int i=1;i<=n;i++) val[i]=read(),s[i]=read(),c[i]=read(),x[i]=read();
    int mx=0;
    for (int i=1;i<=k;i++) q[i]=read(),mx=std::max(mx,q[i]);
    for (int i=0;i<=mx;i++) f[i]=i;
    for (int i=1;i<=n;i++)
        if (!x[i]) beg[mx].push_back(i);
        else beg[std::min((c[i]-1)/x[i]+1,mx)].push_back(i);
    for (int i=mx;i>=1;i--)
    {
        for (int j=0;j<beg[i].size();j++)
        {
            int id=beg[i][j];
            if (!ob[id]) que.push(mp(val[id]+s[id],id));
            else que.push(mp(val[id],id));
        }
        while (da[i]<m)
        {
            if (que.empty()) break;
            int u=que.top().first,id=que.top().second;que.pop();
            ans[mx]+=u;tmp[i].push(mp(u,i));Q.push(mp(-u,i));da[i]++;ob[id]++;
            if (c[id]-get_sum(id,i-1)-ob[id]>0) que.push(mp(val[id],id));
            else if (get_sum(id,i-1)>0) beg[i-1].push_back(id);
        }
        if (da[i]==m) f[i]=i-1;
    }
    for (int i=mx;i>1;i--)
    {
        ans[i-1]=ans[i];
        int tot=0;
        while (!tmp[i].empty()) a[++tot]=tmp[i].top().first,tmp[i].pop(),Q.erase(mp(-a[tot],i));
        std::reverse(a+1,a+tot+1);
        while (find(i-1)>0&&tot)
        {
            int id=find(i-1);
            tmp[id].push(mp(a[tot],id));
            Q.push(mp(-a[tot],id));
            tot--;da[id]++;
            if (da[id]==m) f[id]=id-1;
        }
        while (!Q.empty()&&tot&&-Q.top().first<a[tot])
        {
            int u=Q.top().first,id=Q.top().second;
            ans[i-1]+=u;Q.pop();
            tmp[id].erase(mp(-u,id));tmp[id].push(mp(a[tot],id));
            Q.push(mp(-a[tot],id));tot--;
        }
        while (tot) ans[i-1]-=a[tot],tot--;
    }
    for (int i=1;i<=k;i++) printf("%lld\n",ans[q[i]]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值