[APIO/CTSC 2007]数据备份

Description

题库链接

数轴上有 (n) 个点,让你选出恰好 (k) 对互异的点对,最小化所有点对间的距离和。

(1leq 2kleq nleq 100000)

Solution

首先一个显然的贪心是每个点对一定都是相邻的点。

(f_{i, j, 1/0}) 表示前 (i) 个点中,选出 (j) 个点对,第 (i) 个点是/否被选的最小距离和。转移就考虑第 (i) 个点和第 (i-1) 个点是否组成点对。

这是一个 (O(nk)) 的 DP。不过考虑 (g(x)) 表示恰好选 (x) 个点对时的最小距离和,可以证明 (g) 是下凸的。因此可以带权二分优化。二分后的 DP 可以直接去掉第二维,每连接一个点对就减去二分的权值。只不过要开一个辅助数组记录连了几个点对。

当然有时候会有二分不到目标点的情况。这是因为目标点与相邻的点共线。那么可以做如下的处理:当 (f) 值相等时,取最少的连线对数。那么当前二分的权值算出的点对数 (leq k) 时,就对答案进行一次更新:(f+k imes mid)。由于凸函数性质,如果这一算式是错解那么它一定不会成为最终的答案,因此不会有误算的情况。

二分上下界:下界取 (0),此时一个点对都不取最优;上界取坐标的最大值,因为每次连边的代价为负,因此所有点都选上最优。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 100000+5;
const ll inf = 1e16;

int n, k, g[N][2], a[N];
ll f[N][2], tmp;

bool check(ll m) {
    f[1][0] = g[1][0] = 0;
    f[1][1] = inf;
    for (int i = 2; i <= n; i++) {
        f[i][0] = f[i-1][0], g[i][0] = g[i-1][0];
        if (f[i-1][1] < f[i][0] || (f[i-1][1] == f[1][0] && g[i-1][1] < g[i][0]))
            f[i][0] = f[i-1][1], g[i][0] = g[i-1][1];
        f[i][1] = f[i-1][0]+a[i]-a[i-1]+m, g[i][1] = g[i-1][0]+1;
    }
    ll mf, mg;
    mf = f[n][f[n][1] < f[n][0] || (f[n][1] == f[n][0] && g[n][1] < g[n][0])];
    mg = g[n][f[n][1] < f[n][0] || (f[n][1] == f[n][0] && g[n][1] < g[n][0])];
    if (mg <= k) return tmp = mf-m*k, true;
    return false;
}
int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    int l = -1e9, r = 0, mid; ll ans = -1;
    while (l <= r) {
        mid = (l+r)>>1;
        if (check(mid)) r = mid-1, ans = tmp;
        else l = mid+1;
    }
    printf("%lld
", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/NaVi-Awson/p/13503681.html