• CF335F Buy One, Get One Free


    Description

    Luogu传送门

    Solution

    神仙贪心题。

    关于基础的反悔贪心,详见博客浅谈反悔贪心

    首先进行一些预处理,把物品从大到小排序,并按价值分组,即相同价值的放一起(能否白嫖只与比当前物品价值更大的物品有关)。

    错误的贪心:能白嫖就白嫖(观察样例就知道显然是错误的)。

    还是考虑通过反悔来使它正确。

    开一个小根堆,记录每一次贪心白嫖到的价值。

    枚举每一组物品,设枚举到第 (i) 组,有 (sum_i) 个,先计算出能直接白嫖的物品数量。假设大于当前物品价值的有 (num) 个物品,白嫖的物品个数是 (q.size()),那么数量就是: $$p = min(num - 2 imes q.size(), sum_i)$$

    (白嫖一个物品的条件是买一个价值比它大的物品,所以用掉的物品数是 (2 imes q.size())

    这时,我们要开一个数组来记录当前轮能白嫖哪些物品,而不是直接塞到小根堆里。因为直接塞到小根堆里的话,会影响当前轮后面物品的选择。

    我们把 (p) 个物品直接压到数组里,然后考虑当前组剩下的物品,数量为 (tot = min(num, sum_i) - p)(应该比较好理解吧)

    枚举这 (tot) 个物品,并与之前白嫖到的物品判断(即小根堆里的物品)。

    注意:在此之前所有买或者白嫖的物品价值都大于当前组物品价值,但是小根堆里的值可能会小于当前组物品价值,因为里面还有一些为了达到反悔效果而塞进去的数。当然,不可否认的是,小根堆里的每一个元素都代表着一个物品。

    取出当前堆顶,设为 (k),让 (k) 与当前组价值 (val_i) 做比较。

    • (k < val_i):此时白嫖 (k) 显然不如白嫖 (val_i) 优,那么我们把原本用来白嫖 (k) 的机会拿来白嫖 (val_i),且我们要购买 (k),因此又多了一个白嫖机会 ,所以再多白嫖一个 (val_i)

    • (k geq val_i)(k)(val_i) 更优,我们先把 (k) 再放回去。考虑如何达到反悔效果,假设我们是选择价值为 (x(x > k)) 的物品来白嫖的 (k)。通过上面一种情况,我们知道,如果买 (k),可以多白嫖两个 (val_i),因此我们再来分类讨论一下。

      • (x)(2 imes val_i),嫖 (k):这时我们需要花费的代价是 (x + 2 imes val_i)
      • (x)(k),嫖 (2 * val_i):这时我们的代价是 (x + k)

      二者做一个差,为 (res = 2 imes val_i - k),我们把 (res) 当作一个物品压到数组里即可。

    在上述过程中,如果直接放到堆里,(res) 可能会成为堆顶,就会出现自己嫖自己的情况。

    反悔贪心的过程就结束啦。最后,我们对所有物品求个和,减去堆中的所有元素和就是最少需要花费的代价啦。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #include <algorithm>
    #define ll long long
    
    using namespace std;
    
    const ll N = 5e5 + 10;
    ll n, ans;
    ll a[N];
    ll val[N], sum[N], cnt;
    priority_queue <ll, vector<ll>, greater<ll> > q;
    ll stk[N], top;
    
    inline bool cmp(ll a, ll b){
        return a > b;
    }
    
    signed main(){
        scanf("%lld", &n);
        for(ll i = 1; i <= n; ++i)
            scanf("%lld", &a[i]), ans += a[i];
        sort(a + 1, a + 1 + n, cmp);
        for(ll i = 1; i <= n; ++i){
            if(i == 1 || a[i] != a[i - 1]) val[++cnt] = a[i];
            sum[cnt]++;
        }
        ll p, tot, num = 0;
        for(ll i = 1; i <= cnt; ++i){
            p = min(num - 2 * (ll)q.size(), sum[i]);
            tot = min(sum[i], num) - p;
            top = 0;
            for(ll j = 1; j <= p; ++j)
                stk[++top] = val[i];
            for(ll j = 1; j <= tot; j += 2){
                ll k = q.top();
                q.pop();
                if(k < val[i]){
                    stk[++top] = val[i];
                    if(j < tot) stk[++top] = val[i];
                }else{
                    stk[++top] = k;
                    if(j < tot) stk[++top] = (val[i] << 1) - k;
                }
            }
            for(ll j = 1; j <= top; ++j)
                if(stk[j] >= 0) q.push(stk[j]);
            num += sum[i];
        }
        while(!q.empty())
            ans -= q.top(), q.pop();
        printf("%lld
    ", ans);
        return 0;
    }
    

    End

    本文来自博客园,作者:xixike,转载请注明原文链接:https://www.cnblogs.com/xixike/p/15519911.html

  • 相关阅读:
    正则表达式知识
    网页边上的弹窗
    表格的搜索
    表格的删除与添加
    添加标签和删除标签
    延迟提示框
    js知识
    反射的应用
    java反射知识
    事务的特性和隔离级别
  • 原文地址:https://www.cnblogs.com/xixike/p/15519911.html
Copyright © 2020-2023  润新知