「CTSC 2007」数据备份

题目大意:

为了使布线长度尽量小,每对布线的办公楼一定是相邻的,所以我们可以在读入时计算差分数组保存每相邻两个办公楼的距离,这样问题转化为,在差分数组 $b$ 中找 $k$ 个数,满足 $k$ 个数之和最小且互不相邻。

考虑一个最小的数 $x$,以及它旁边的两个数这样的组合:$(l,x,r)$

对于这 $3$ 个数,要么取 $x$,要么取 $l$ 和 $r$。不会只取 $l$ 或 $r$,因为不如取 $x$ 好:$x$ 既小,又不会对这 $3$ 个数之外的产生限制。

所以我们就有了下面这个很妙的贪心:

  • 每次选最小的数 $x$,累计答案。
  • 把 $3$ 个数缩起来:把 $x$ 及其两边的数删去,插入一个值为 $b[l]+b[r]−b[x]$ 的数。

进行 $k$ 次上述过程。

#include <cstdio>
#include <queue>

typedef long long LL;
const int N = 200005;

struct Pair {
    LL x; int y;
    bool operator < (const Pair &rhs) const {
        return x > rhs.x;
    }
};
std::priority_queue<Pair> Q;
int n, m, a[N], L[N], R[N]; LL b[N], ans; bool vis[N];

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    --n;
    for (int i = 1; i <= n; ++i) b[i] = a[i+1] - a[i];
    for (int i = 1; i <= n; ++i)
        L[i] = i - 1, R[i] = i + 1, Q.push((Pair){b[i], i});
    b[0] = b[n+1] = b[n+2] = 1e18, L[0] = R[n+1] = n + 2;
    for (int i = 1; i <= m; ++i) {
        Pair now = Q.top();
        while (vis[now.y]) Q.pop(), now = Q.top();
        Q.pop(), ans += now.x;
        int l = L[now.y], r = R[now.y];
        vis[l] = vis[r] = true;
        L[now.y] = L[l], R[now.y] = R[r], R[L[now.y]] = L[R[now.y]] = now.y;
        b[now.y] = b[l] + b[r] - b[now.y];
        Q.push((Pair){b[now.y], now.y});
    }
    printf("%lld\n", ans);
    return 0;
}