Codeforces1555E Boring Segments (思维 线段树) (线段树好题!)

99 篇文章 2 订阅
69 篇文章 5 订阅

题目链接: Boring Segments

超级推荐这个题, 感觉这个题超赞!

大致题意

给定n条线段, 每条线段覆盖区间为[l, r], 花费为c.

现在可以从中任意选择若干条线段, 使得这些线段覆盖[1, m]区间.
而我们选择这些线段的代价是: 被选择的线段中花费最大值 - 花费最小值.

问: 最小的代价是多少. (题目保证一定存在解.)

解题思路

思维

一般求极值的题目, 尺取法是很常见的一种做法.

对于本题而言, 我们希望极值之差最小, 相当于我希望选择的这若干条线段花费近可能接近.
由于我们可以从n条线段中任意选择, 因此我们不妨考虑对于所有线段按照花费从小到大排序.

接下来把排序后的所有线段依次编号1~n:
此时我们可以枚举, 当我们选择第r条线段时, 考虑l ∈ [1, r]区间的线段, l的最大合法取值是多少.
这样选择编号[l, r]的线段即可满足要求, **代价为: c r − c l c_r - c_l crcl​ **.

这里枚举r的复杂度为O(n), 如果l暴力去枚举, 复杂度会爆炸.
但我们可以发现, 当r增大时, l其实是单调的, 因此我们可以用双指针的做法维护[l, r].


线段树

我们刚才已经理清了如何去进行枚举答案. 但里面有很重要的一点, 我们怎么知道此时选择线段[l, r]是否能够覆盖区间[1, m]呢?

权值线段树维护区间覆盖情况.
即: 建立权值线段树维护区间值域, 叶节点记录每个点被覆盖次数, 树内维护当前区间的所有点中, 被覆盖的次数最少的点(维护最小值). 这样当我们想知道[1, m]区间是否已经被覆盖时, 我们可以查询根节点的最小值是否大于等于1.


接下来来说说代码细节方面:

这个题很特别的一点就是, 如果一条线段覆盖[3, 5], 另外一条覆盖[6, 10]. 我们不可以认为[3, 10]区间都被覆盖了 . 因为题目认为5和6之间是没有被覆盖的部分. 相当于线段与线段的端点必须重合.

那么我们怎么处理这样的区间覆盖问题呢?

比赛的时候我想到了个歪点子, 我认为对于一条线段覆盖[L, R], 其中[L+1, R-1]被覆盖2次, 而L和R只被覆盖一次. 每次我判断根节点的最小值是否大于等于2即可. (1号点和m号点我默认覆盖过一次了.)


然而… 傻子想法. 我先覆盖一次[1, 5], 再覆盖一次[6, 10], 然后我再覆盖一次[1, 5], 我再覆盖一次[6, 10]. 其实区间是没有被完全覆盖的. (我居然还调了半天)

我们记录线段时, 把区间[L, R]记录成[L, R-1]即可. 这样我们树内维护的区间也相应的变为[1, m-1].
(比赛时也不是没想过记录成开区间, 但是最后没想明白把自己给否掉了QAQ)


感觉这个题真的非常好, 包括但不限于: 极值–>尺取, 区间覆盖–>线段树, 区间覆盖处理方式.

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E6 + 10;
struct node {
    int l, r;
    int val; //维护线段覆盖最小值
    int lazy;
}t[N << 2];
void pushdown(node& op, int lazy) { op.val += lazy, op.lazy += lazy; }
void pushdown(int x) {
    if (!t[x].lazy) return;
    pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
    t[x].lazy = 0;
}
void pushup(int x) { t[x].val = min(t[x << 1].val, t[x << 1 | 1].val); }

void build(int l, int r, int x = 1) {
    t[x] = { l, r, 0, 0 };
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}

void modify(int l, int r, int c, int x = 1) {
    if (l <= t[x].l and r >= t[x].r) {
        pushdown(t[x], c);
        return;
    }
    pushdown(x);
    int mid = t[x].l + t[x].r >> 1;
    if (l <= mid) modify(l, r, c, x << 1);
    if (r > mid) modify(l, r, c, x << 1 | 1);
    pushup(x);
}

struct line {
    int l, r, c;
    bool operator< (const line& t) const { return c < t.c; }
}area[N];
int main()
{
    int n, m; cin >> n >> m;
    build(1, m - 1);
    
    rep(i, n) {
        int l, r, c; scanf("%d %d %d", &l, &r, &c);
        area[i] = { l, r - 1, c };
    }
    sort(area + 1, area + 1 + n);
    
    int res = 0x3f3f3f3f, L = 1, R = 0;
    while (R + 1 <= n) {
        const auto& [l, r, c] = area[++R];
        modify(l, r, 1);
        if (!t[1].val) continue;
        
        while (L < R) {
            const auto& [l, r, c] = area[L];
            modify(l, r, -1);
            
            if (t[1].val) ++L;
            else {
                modify(l, r, 1);
                break;
            }
        }
        res = min(res, area[R].c - area[L].c);
    }
    
    printf("%d\n", res);
    return 0;
}

END

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值