NC208250 牛牛的最美味和最不美味的零食

发布时间 2023-05-02 17:07:50作者: 空白菌

题目链接

题目

题目描述

牛牛为了减(吃)肥(好),希望对他的零食序列有更深刻的了解,所以他把他的零食排成一列,然后对每一个零食的美味程度都打了分,现在他有可能执行两种操作:

eat k:吃掉当前的第k个零食。右边的零食全部往左移动一位(编号减一)。

query i j:查询当前第i个零食到第j个零食里面美味度最高的和最低的零食的美味度。

输入描述

第一行包含两个数n, m,表示原始数组的元素个数和操作的个数。第二行包括n个数,表示原始数组。以下m行,每行格式为1 k或者2 i j,其中第一个数为1表示吃掉,为2表示询问。

输出描述

对每个询问操作输出一行,包括两个数,表示该范围内的最小值和最大值。

示例1

输入

10 4
1 5 2 6 7 4 9 3 1 5
2 2 8
1 3
1 6
2 2 8

输出

2 9
1 7

说明

\(1 \leq n, m \leq 10^6\)\(1 \leq m \leq 10^6\) ,数组中的元素绝对值均不超过 \(10^9\)

题解

知识点:线段树,二分。

线段树本身不支持删除操作,我们考虑用区间有效点数代替删除的效果。

比较好的方法是用相对坐标表示查找的点,这样就可以利用线段树上二分来锁定我们要找的点或区间。

要删除当前第 \(k\) 个点,有两种情况:

  1. 若左子区间的有效点数大于等于 \(k\) ,那么要修改的点就在左子区间。
  2. 否则在右子区间。

要查询当前区间 \([l,r]\) 的值也是同理,有三种情况:

  1. 若左子区间的有效点大于等于 \(r\) ,则查询左子区间。
  2. 否则若左子区间的有效点小于 \(l\) ,则查询右子区间。
  3. 否则可以确定查询区间跨越了左右子区间,则两边都要查询。

这道题展示了线段树上二分查找点、区间的基本写法,是个值得一做的好题。

时间复杂度 \(O((n+m) \log n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct T {
    int sum;
    int mi;
    int mx;
    static T e() { return { 0,(int)2e9,(int)-2e9 }; }
    friend T operator+(const T &a, const T &b) {
        return {
            a.sum + b.sum,
            min(a.mi, b.mi),
            max(a.mx,b.mx)
        };
    }
};
class SegmentTree {
    int n;
    vector<T> node;

    void update(int rt, int l, int r, int x) {
        if (l == r) return node[rt] = { 0,(int)2e9,(int)-2e9 }, void();
        int mid = l + r >> 1;
        if (node[rt << 1].sum >= x)update(rt << 1, l, mid, x);
        else update(rt << 1 | 1, mid + 1, r, x - node[rt << 1].sum);
        node[rt] = node[rt << 1] + node[rt << 1 | 1];
    }

    T query(int rt, int l, int r, int x, int y) {
        if (x == 1 && node[rt].sum == y) return node[rt];
        int mid = l + r >> 1;
        if (node[rt << 1].sum >= y) return query(rt << 1, l, mid, x, y);
        else if (node[rt << 1].sum < x) return query(rt << 1 | 1, mid + 1, r, x - node[rt << 1].sum, y - node[rt << 1].sum);
        else return query(rt << 1, l, mid, x, node[rt << 1].sum) + query(rt << 1 | 1, mid + 1, r, 1, y - node[rt << 1].sum);
    }

public:
    SegmentTree(int _n = 0) { init(_n); }
    SegmentTree(int _n, const vector<T> &src) { init(_n, src); }

    void init(int _n) {
        n = _n;
        node.assign(n << 2, T::e());
    }
    void init(int _n, const vector<T> &src) {
        init(_n);
        function<void(int, int, int)> build = [&](int rt, int l, int r) {
            if (l == r) return node[rt] = src[l], void();
            int mid = l + r >> 1;
            build(rt << 1, l, mid);
            build(rt << 1 | 1, mid + 1, r);
            node[rt] = node[rt << 1] + node[rt << 1 | 1];
        };
        build(1, 1, n);
    }

    void update(int x) { update(1, 1, n, x); }

    T query(int x, int y) { return query(1, 1, n, x, y); }
};

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<T> a(n + 1);
    for (int i = 1;i <= n;i++) {
        int x;
        cin >> x;
        a[i] = { 1,x,x };
    }
    SegmentTree sgt(n, a);
    while (m--) {
        int op;
        cin >> op;
        if (op == 1) {
            int k;
            cin >> k;
            sgt.update(k);
        }
        else {
            int i, j;
            cin >> i >> j;
            auto [sum, mi, mx] = sgt.query(i, j);
            cout << mi << ' ' << mx << '\n';
        }
    }
    return 0;
}