【NOI2017】蔬菜【哈希表+并查集+贪心】

题目链接 LOJ 2306


  这道题,说难不难——是假的,总之不简单,做了真的好久了。(大雾

  首先,分析这道题的输出时间,也就是说明了它是没有线性关系的这么一件事。这点倒是显而易见的,虽然说随着天数的增长,收益也是单调不减的,但是增长曲线绝非线性。

  所以,这里先去考虑如何处理给出来的几个值的问题了。

  首先是\large a_i\large s_i,首次销售有额外的收益,于是不妨看成只有一个物品,并且这个物品的收益额还是\large a_i + s_i,于是乎,剩下了\large c_i - 1个物品的价值就都是\large a_i了。

  接下去,就是要怎样的选择了,首先,贪心的想,我们肯定是把价值最高的先要拿到,但是真实时候,我们肯定是按照期限卖的,先将它的期限卖完,也就是我们需要知道它的期限的那天,然后先去把那天给卖完了,再往前看,也就是倒着的过程,可以画图来说明问题。

  我们肯定是将其放在期限日开始往前填充,最大值放在该天,然后向前填充到填充结束,填充满之前所有的天数,或者是物品用完。

  于是,根据这样的贪心方法,就有可能存在这样的情况:

其实,这样的情况,刚好就是可以用哈希表来解决,也就是使用并查集来维护一个哈希表即可。找到每个日期的前一个空格(没有被完全填满)的地方。

  好了,经过上述的操作,我们就知道了第MAX天的最大收益了,但是我们还不能知道第Qi天的收益最大值到底是多少呢?但是,我们可以知道,既然都满足了第MAX天,那么当然也可以倒推回来第MAX-1天,以此类推,也可以知道之前所有天的最大收益了。

  但是,从后往前直接减去肯定不行的,我们现在得到的是一个稀疏哈希表,实际上每个值,每一个阴影块都是可以向前移动的,所以,这肯定是要求一个前缀的冗余空间了,将前缀冗余空间确定出来,我们就是知道了前面有多少空间是空白的,我们可以向前移动的路程了。

  知道前面的冗余空间了,那么我们就想知道原来操作的时候打在后头的所谓期限日的占用空间,我们需要满足的是目前的占用空间是要小于等于冗余空间的话,就是可以向前移动而不用减去,如果是大于了的话,就是说明,我们要先去减去那些贡献小的那部分了。因为,满足条件的一件事就是能算入贡献的那些个价值,一定是能取之前的任意一天的,这是哈希表所带来的绝对好处。——也就是说,对于所有放入的价值,肯定是先删除价值小的。

  于是,这里就表示了,我们一开始操作的时候,不妨就是模拟一个栈,一步步的操作,我们把操作的过程拿栈存储下来,方便我们之后的删除操作。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <bitset>
//#include <unordered_map>
//#include <unordered_set>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
const int maxN = 2e5 + 7;
int N, M, Q, ques[maxN], root[maxN], tot, max_date, date_have[maxN], cnt, g[maxN], ga[maxN];
ll ans[maxN], sum, las_pre[maxN], R, upd[maxN];
inline int fid(int x) { return x == root[x] ? x : root[x] = fid(root[x]); }
struct node
{
    int c, a, x, date;  //库存、单价、每日减少量、截止日期
    node(int _c=0, int _a=0, int _x=0, int _d=0):c(_c), a(_a), x(_x), date(_d) {}
    friend bool operator < (node e1, node e2) { return e1.a > e2.a; }
} A[maxN];
int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
    scanf("%d%d%d", &N, &M, &Q);
    for(int i=1, ai, si, ci, xi; i<=N; i++)
    {
        scanf("%d%d%d%d", &ai, &si, &ci, &xi);
        A[++tot] = node(1, ai + si, 0, xi ? (ci - 1) / xi + 1 : INF);
        if(ci > 1) A[++tot] = node(ci - 1, ai, xi, xi ? (ci - 2) / xi + 1 : INF);
    }
    for(int i=1; i<=Q; i++) { scanf("%d", &ques[i]); max_date = max(max_date, ques[i]); }
    sort(A + 1, A + tot + 1);
    for(int i=0; i<=max_date; i++) root[i] = i; //init
    for(int i=1, idx, res, now; i<=tot; i++)
    {
        cnt++;  //正着过程的操作,反过来拎出去
        idx = fid(min(A[i].date, max_date));    //找到前一个哈希表中的空缺项
        res = (idx - 1) * A[i].x;   //当天之前用掉了多少
        now = A[i].c - res; //当天还剩多少
        while(idx && now)   //期限清空or该物使用完了
        {
            int tmp = M - date_have[idx], minn = min(tmp, now); //tmp是当天还有多少可以填充进去的,minn是可填充量和该物品剩余量的取小值
            sum += 1LL * minn * A[i].a;
            g[cnt] += minn; //本次操作放入该物品的个数
            ga[cnt] = A[i].a;   //这次操作的物品的价值
            date_have[idx] += minn; //当天放入
            if(date_have[idx] == M) //放满了,那么这一天就满了,哈希表减去了一个单元格
            {
                int u = fid(idx), v = fid(idx - 1);
                if(u ^ v) root[u] = v;
            }
            now -= minn;
            int p = idx; idx = fid(idx - 1); p -= idx;  //要么这一天放满,要么物品用完
            if(res) { now += p * A[i].x; res -= p * A[i].x; }   //这一天放满的情况
        }
        if(!fid(max_date)) break;   //哈希表满了
    }
    for(int i=1; i<=max_date; i++) las_pre[i] = M - date_have[i] + las_pre[i - 1];  //求冗余量,前缀和积累
    for(int i=max_date; i>=1; i--)
    {
        upd[i] = max(R - las_pre[i], 0LL);    //截止到目前为止应当删除的量(从后往前算起)
        R += date_have[i];
    }
    for(int i=max_date; i>=1; i--)
    {
        ans[i] = sum;
        ll tmp = upd[i - 1] - upd[i], minn; //tmp是这一天的放入个数,要删除
        while(tmp)
        {
            minn = min(tmp, 1LL * g[cnt]);
            sum -= 1LL * ga[cnt] * minn;
            tmp -= minn; g[cnt] -= minn;
            if(!g[cnt]) cnt--;
        }
    }
    for(int i=1; i<=Q; i++) printf("%lld\n", ans[ques[i]]);
    return 0;
}
发布了818 篇原创文章 · 获赞 978 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/qq_41730082/article/details/104581405
今日推荐