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;
}