题解 [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\) 之差的做法。
如果我们扫到一个数,其它啥也没有,那就只能给它加上/减去了。如果后面又来了一个数,且加减是和前面一个相反的,那就有了多种选择了。

  1. 继续给它补上,花费 \(x\)\(y\)
  2. 把前面的某一个给换过来,花费 \(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;
}
posted @ 2021-08-04 09:09  Acfboy  阅读(50)  评论(0)    收藏  举报