[SOJ603] 环【贪心】

时间:2019-10-15
本文章向大家介绍[SOJ603] 环【贪心】,主要包括[SOJ603] 环【贪心】使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题意简述:数轴上有\(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;
}

原文地址:https://www.cnblogs.com/suwakow/p/11677547.html