cf1555 E. Boring Segments

题意:

有 n 个闭区间,每个区间有左右端点 l,r 和价值 w。要求选出一个区间集合,集合中的区间能覆盖线段 \([1,m]\),且价值最大的区间与价值最小的区间的价值之差最小。

保证答案存在

\(1\le l_i < r_i\le m\)

思路:

错解:二分,check不知咋写

正解:双指针,维护取的所有区间的最小价值 \(l\) 和最大价值 \(r\),那么当 \(l\) 右移时 \(r\) 不左移

用一棵区间加、区间最值的线段树维护 \([1,m]\) 是否被覆盖。只需要记录区间最小值和节点懒标记!

节点记录某点被覆盖的次数。若区间最小值为0则区间未被完全覆盖。查询时只需看根节点最小值即可,不用写 query 函数

有个细节:\([1,2],[3,4]\) 是不能覆盖 \([1,4]\) 的。为此只需把所有区间看成左闭右开的,即所有区间右端点 -1 即可!

const signed N = 3 + 1e6;
int n, m;

#define mid ((l+r)/2)
#define lson u*2
#define rson u*2+1
#define ls lson,l,mid
#define rs rson,mid+1,r
int mi[N*4], lz[N*4]; //最小值、懒标记
void pushup(int u) {
    mi[u] = min(mi[lson], mi[rson]);
}
void pushdown(int u, int l, int r) {
    if(lz[u])
        mi[lson] += lz[u], lz[lson] += lz[u],
        mi[rson] += lz[u], lz[rson] += lz[u],
        lz[u] = 0;
}
void upd(int u, int l, int r, int x, int y, int d) {
    if(x <= l && r <= y) return mi[u] += d, lz[u] += d, void();
    pushdown(u, l, r);
    if(x <= mid) upd(ls, x, y, d);
    if(y > mid) upd(rs, x, y, d);
    pushup(u);
}

signed main() {
    iofast;
    cin >> n >> m; m--; //细节-1
    vector<array<int,3>> segs(n);
    for(auto &[l,r,w]: segs) cin >> l >> r >> w, r--; //细节-1
    sort(all(segs), [](auto &a, auto &b) {
        return a[2] < b[2]; 
    });
    
    int ans = INF;
    for(int l = 0, r = -1; l < n; l++) {
        while(!mi[1] && r + 1 < n)
            ++r, upd(1, 1, m, segs[r][0], segs[r][1], 1);
        if(!mi[1]) break; //没希望了
        ans = min(ans, segs[r][2] - segs[l][2]);
        upd(1, 1, m, segs[l][0], segs[l][1], -1); 
    }
    cout << ans;
}
posted @ 2022-05-18 14:58  Bellala  阅读(19)  评论(0)    收藏  举报