CF335F Buy One, Get One Free 题解

题目简述

  • \(n\) 个物品,每个物品标价 \(a_i\),可以买 \(i\)\(j\) 当且仅当 \(a_i>a_j\),求买所有物品的最小代价

分析

  • 规定,\((a\to b)\) 表示:付费买 \(a\)(编号 或 代价)的物品,白嫖 \(b\)(编号 或 代价)的物品

  • 考虑反悔贪心。

  • 符合直觉的,我们有选 \(i\) 尽可能白嫖比 \(a_i\) 便宜的,最大的 \(a_j\)

  • 然而,我们发现:若有两个代价为 \(z\) 的物品,以前有选择 \((x\to y)\)

    • 与其选择 \((x\to y),(a\to 0),(a\to 0)\) 不如选择 \((x\to a),(y\to b)\)
  • 由此,考虑用小根堆维护 白嫖代价(与两个物品相关的的白嫖方案所产生的代价),注意:我们不维护选择的物品

  • 对于代价一致的物品,化为一组一起考虑

  • 物品代价从大到小,依次考虑一组物品分配,记:当前物品代价为 \(num\)

    • 若在选择上一组的物品时有所剩余,不妨令 \((\text{剩余}\to \text{当前})\),毕竟由于我们从大到小选取。
      • 具体的就是增加一个白嫖代价为 \(num\) 的方案。
    • 设:我们剩下 \(cnt\) 个物品
    • 尝试:是否可以变更之前的白嫖方案,从而使当前更优。
    • 从小根堆中选取一个方案,白嫖代价记为 \(w\)
    • \(w<num\),咦,我们好像可以尝试白嫖 \(num\),从而更优
      • 由于一个白嫖关系是与两个物品相关的,我们必定可以确定在这组关系中被白嫖的那个物品,我们不妨让被白嫖的那个物品不白飘了,转而让 \(num\) 白嫖(对啦你一定还记得我们物品是从大到小加入的)
      • 相当于什么,删除 \(w\) 的方案,转而增加白嫖代价为 \(num\) 的方案。
      • 可是,这真对吗?其实我们少考虑了一种情况,我们可不可以选择两个 \(num\),并且,让被白嫖的那个物品不白嫖,而这两个 \(num\) 正好安排到原白嫖关系的两个物品麾下。
      • 于是,若有大于等于 \(2\)\(num\),删除 \(w\) 方案,增加两个 \(num\) 方案,若有只 \(1\)\(num\),删除 \(w\),增加 \(1\)\(num\) 方案。
    • \(w> num\),哎呦,这个嘛,我们好像可以尝试白嫖两个 \(num\),从而更优
      • 没有两个 \(num\) 呢,那就啥也不干
      • 存在的话,我们与当前白嫖方案相关的两个物品,我们必定可以不白嫖转而,白嫖两个 \(num\)
      • 具体的,若 \(w<num\times 2\),我们删除 \(w\) 的方案,转而增加两个 \(num\) 的方案。
      • 可以这样真对吗,我们敏锐的发现 \(num<w\) 我们不要了 \(w\) 的代价,选择了 \(num\)。那 \(num\) 在将来会不会也作为小根堆的最小值被破话,如果破坏的话,我们真的应该把贡献 从 \(num\times 2\) 变成 \(num+\cdots\) 吗?
      • 此时,我们不妨把 \(num\times 2\) 还原成 \(w\),然后直接把两个剩余的 \(num\) 拆散做新的贡献。毕竟都是从大到小枚举,所有大的物品对小的物品来讲都是相同的,那为什么不把 \(w\) 还原呢?
      • 于是我们重新考虑,若 \(w<num\times 2\),我们保留 \(w\) 的方案,转而增加一个 \(num\times 2-w\) 的方案。而,\(w>num\times2-w\) 恰好满足,若是拆散一对,我们必定是从后往前拆散的。
      • 反思:那为什么 \(w<num\) 时,我们没有做 \(num-w\) 的贡献,对于只有一个 \(num\) 的我们那种操作会剩下一个物品,构不成我们所谓的有限队列里存的与两个数相关的白嫖方案。而若是有多个 \(num\)\(num-w\) 是撤销时把贡献转回 \(w\),但是显然 \(num>w\) 呀,我们干嘛撤销时不转回到 \(num\),也就是继续拆 \(w\)。综上,这样做是错的。
  • 哎,可能是语文太差了,看不懂别人写的,在参考了数篇课文之后,才算是有了大概得思路。故成此篇,呱!

#include <map>
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
int main()
{
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    ll sum = 0, ans = 0, t, n; cin >> n;
    map<ll, ll, greater<ll>> m; priority_queue<ll> q;
    for (ll i = 1; i <= n; i++) cin >> t, m[t]++, ans += t;
    for (auto [num, cnt] : m)
    {
        vector<ll> tmp; ll ttt = sum - q.size() * 2; sum += cnt;
        while (ttt && cnt) tmp.push_back({ num }), cnt--, ttt--;
        while (cnt && q.size())
        {
            const ll w = -q.top();
            if (w <= num)
            {
                if (cnt)tmp.push_back(num), cnt--;
                if (cnt)tmp.push_back(num), cnt--;
            }
            else if (cnt >= 2 && 2 * num >= w)tmp.push_back(w), tmp.push_back(num * 2 - w), cnt -= 2;
            else break; q.pop(); auto p=q;
        }
        for (ll v : tmp)q.push(-v);
    }
    while (!q.empty())ans += q.top(), q.pop();
    cout << ans << '\n';
    return 0;
}
posted @ 2025-02-10 08:33  LUHCUH  阅读(21)  评论(0)    收藏  举报