题意简述:数轴上有\(n\)条线段,将这\(n\)条线段分成恰好\(k\)组,每一组的价值为组内线段的交集长度,求最大总价值。\(1\leq n, k, \leq 6000\)。
对于一条线段\([l_i, r_i]\),如果存在另一条线段\(j\),使得\(l_i\leq l_j, r_i\geq r_j\),则我们称线段\(i\)是“长的”,否则是“短的”。注意若干条线段完全相同时,最后一条线段不能直接认定成“长的”。由定义知,任意两条“短的”线段互不包含。
- Proof:对于任意一条“长的”线段\(i\),它要么与定义它的\(j\)放在同一组,要么单独在一组。
证明:显然将\(i, j\)放在同一组,\(i\)的加入不会使答案变劣。否则,若\(i, j\)不在同一组,且\(i\)与至少一条线段\(k\)在同一组,则将\(i\)从\(k\)所在的组中取出,放入\(j\)所在的组,答案不会变劣。
将左端点排序,用单调栈维护右端点,可以简单求出所有“长的”线段。显然我们只需要对于每一个\(c\in[0, k]\)考虑长度前\(c\)大的“长的”线段和把“短的”线段分成\(k-c\)组时的最大权值即可。
考虑一个初始状态,所有\(m\)条“短的”线段单独分在一个组,边界为\([l_i, r_i]\)。显然每次合并两个组时,一定是合并相邻的两组最优。
若合并当前的第\(i\)组和第\(i+1\)组,设两组交集的左右边界分别为\([x_i, y_i], [x_{i+1}, y_{i+1}]\),则此次合并会导致的权值损失为\(y_{i+1}-x_i\),同时新的边界为\([x_{i+1}, y_i]\)。显然这两组的合并不会对其他组的合并带来的权值变化产生影响,因此贪心去选权值损失最小的一定最优。暴力扫或者用堆实现都可以,复杂度\(O(n^2)\)或\(O(n\log n)\)。
值得注意的是,被合并的两组可能交集为空,在这种情况下,合并可能会导致新的权值为负,而实际的权值显然最低为\(0\)。但是如果有任意一组线段交集为空,则此前提下的最优解一定是选出\(r_i-l_i\)最大的\(k-1\)条线段,其余线段均留在空集中。与之前求出的答案取\(\max\)即可。然而出题人为了卡假做法,根本没有这种数据
// 采用了暴力的实现方式
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cassert>
#include <iostream>
#include <algorithm>
#define R register
#define ll long long
using namespace std;
const int N = 6100, inf = 1e9;
int n, k, numL, numS, nxt[N];
struct node {
int l, r;
inline bool operator < (const node &x) const {
return r - l > x.r - x.l;
}
}seg[N], segL[N], segS[N];
ll sumS[N], ans, sumL;
template <class T> inline void read(T &x) {
x = 0;
char ch = getchar(), w = 0;
while (!isdigit(ch)) w = (ch == '-'), ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
x = w ? -x : x;
return;
}
inline bool cmp(const node &a, const node &b) {
return a.l < b.l;
}
int main() {
read(n), read(k);
for (R int i = 1; i <= n; ++i)
read(seg[i].l), read(seg[i].r);
sort(seg + 1, seg + 1 + n);
for (R int i = 1; i < k; ++i)
ans += seg[i].r - seg[i].l;
int lt = 0, rt = inf;
for (R int i = k; i <= n; ++i)
lt = max(lt, seg[i].l), rt = min(rt, seg[i].r);
ans += max(0, rt - lt);
for (R int i = 1; i <= n; ++i) {
int flag = 1;
for (R int j = i + 1; flag && j <= n; ++j)
if (seg[i].l <= seg[j].l && seg[i].r >= seg[j].r)
flag = 0;
if (flag) segS[++numS] = seg[i];
else segL[++numL] = seg[i];
}
sort(segS + 1, segS + 1 + numS, cmp);
for (R int i = 1; i <= numS; ++i)
sumS[numS] += segS[i].r - segS[i].l, nxt[i] = i + 1;
for (R int i = 1; i < numS; ++i) {
int minLos = inf, pos = 0;
for (R int j = 1; nxt[j] <= numS; j = nxt[j]) {
if (segS[nxt[j]].r - segS[j].l < minLos)
pos = j, minLos = segS[nxt[j]].r - segS[j].l;
}
sumS[numS - i] = sumS[numS - i + 1] - minLos;
segS[pos].l = segS[nxt[pos]].l, nxt[pos] = nxt[nxt[pos]];
}
for (R int i = 0; i <= min(numL, k - 1); ++i)
sumL += segL[i].r - segL[i].l, ans = max(ans, sumS[k - i] + sumL);
cout << ans << endl;
return 0;
}
知识兔