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\)。综上,这样做是错的。
- 若在选择上一组的物品时有所剩余,不妨令 \((\text{剩余}\to \text{当前})\),毕竟由于我们从大到小选取。
-
哎,可能是语文太差了,看不懂别人写的,在参考了数篇课文之后,才算是有了大概得思路。故成此篇,呱!
#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;
}

浙公网安备 33010602011771号