[APIO/CTSC 2007]数据备份

Description

题库链接

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

$1\leq 2k\leq n\leq 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\times 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\n", ans);
    return 0;
}
posted @ 2020-08-14 18:05  NaVi_Awson  阅读(145)  评论(0编辑  收藏  举报