题解 [USACO16OPEN]Landscaping P
反悔贪心 yyds!
给出数组 \(a\),每次可以花费 \(x\) 让一个数减去 \(1\),花费 \(y\) 让一个数加上 \(1\),或者花费 \(z\left|i-j\right|\) 让 \(a_i\) 减去 \(1\),\(a_j\) 加上 \(1\)。求让 \(a\) 变成 \(b\) 的最小花费。
数据范围中 \(a_i, b_i \le 10\) 非常的可疑,我一度以为这是什么神奇的建图后跑个分层图什么的,但是想来想去没有思路, dp 和贪心也没有什么想法。
结果原来是反悔贪心!反悔贪心太妙了!每次做每次都想不到!
其实 \(a_i, b_i \le 10\) 不是让我们去搞分层图,而是把每一次的加减和移动都分开做。现在考虑只有 \(1\) 之差的做法。
如果我们扫到一个数,其它啥也没有,那就只能给它加上/减去了。如果后面又来了一个数,且加减是和前面一个相反的,那就有了多种选择了。
- 继续给它补上,花费 \(x\) 或 \(y\)。
- 把前面的某一个给换过来,花费 \(z(i-j) - v\),\(v\) 是前面的花费。
好像有点反悔贪心的样子了。
警告!
光是这个样子还不能使用可反悔贪心!
使用可反悔贪心的一个重要条件是反悔可以叠加!
反悔可以叠加是什么意思呢?在这个问题中,我们的 \(v\) 作为前面的花费,不仅可以是 \(x, y\),也可以是另外一个调换,因为 \(i-j + (j-k) = i-k\),所以你把那个换的给反悔了就相当于把这个接到换的哪里去了,这样的性质我把它称为“反悔可以叠加”,有了这个性质,才能确保反悔的正确性。
那么接下来的事情就好做了。
\(z(i-j) - v = zi-zj-v = zi -(zj+v)\) 把 \(zj+v\) 扔进大根堆就可以确保这一步加上去的最小了,而反悔的可叠加性也保证了这里的“局部最优”就会转化为“全局最优”。
代码
代码中用 sn
区分了要加或减的两种情况。
#include <iostream>
#include <queue>
#define int long long
std::priority_queue<int> q[2];
int c[2], n, z, ans;
int abs(int x) { return (x > 0) ? x : -x; }
signed main() {
std::cin >> n >> c[1] >> c[0] >> z;
for (int i = 1, a, b, sn; i <= n; i++) {
std::cin >> a >> b;
if (a == b) continue;
sn = a > b;
for (int j = 1; j <= abs(a-b); j++)
if (q[!sn].empty() || i*z - q[!sn].top() > c[!sn])
ans += c[!sn], q[sn].push(i*z + c[!sn]);
else ans += i*z - q[!sn].top(),
q[sn].push(i*z+i*z-q[!sn].top()), q[!sn].pop();
}
std::cout << ans;
}