CF1990F Polygonal Segments 题解

题目链接:https://codeforces.com/contest/1990/problem/F

赛时想到了一个略显抽象的做法,但因为写反了一个判断导致没能过掉。赛后调参卡过,用时 \(3.5/8\) 秒。为了不丢失这个 idea 最终还是决定写个题解记录一下。

题意简述

给定一个数组 \(a_{1..n}\),执行以下查询:

  1. 查询区间 \([l, r]\) 内,最长的“多边形区间”子区间。一个区间为多边形区间,当且仅当其中的数可以成为一个多边形的边长。

  2. \(a_i\) 修改为 \(x\)

\(n \le 2 \times 10^5\)\(q \le 10^5\)

题解

首先很容易看出“多边形区间”的条件是区间最大值小于其余值之和,即 \(\max_{i\in[l,r]}a_i < \dfrac{1} 2 \sum_{i=l}^r a_i\)

由于条件与最大值有关(加上前一天多校题的启发),可以想到一个笛卡尔树结构的搜索:

  • 求横跨最大值区间的答案——很显然全区间最优。

  • 根据最大值将剩余区间分为两部分分别搜索。

使用线段树维护带修区间求和以及求最大值,则这个做法的复杂度是 \(O(qn\log n)\) 的,因为笛卡尔树的大小为 \(n\)

即使时限有足足八秒,这个复杂度也不够过题。聪明的人可能已经发现了,我们可以记忆化这个搜索过程,以避免每次查询重新搜索。每次修改需要删除包括该元素的区间,以保证正确性。由于每次修改影响到的区间非常少,复杂度是比较优秀的,具体可以参考官解。

但我选择了另一条复杂度差一点的道路:剪枝。我们发现,如果可以控制搜索只搜索长度至少为 \(B\) 的区间,则复杂度降为 \(O(q \dfrac {n\log n} B)\),因为每个叶节点的父节点长度至少为 \(B\),那么叶节点至多有 \(2 \dfrac n B\) 个。

对于小于 \(B\) 的答案应该如何处理?若每次查询都扫描一遍小于 \(B\) 的答案,则仍然需要 \(O(n B)\) 的时间复杂度。注意到修改是单点的,而单次修改至多影响 \(O(B)\) 个这样的答案。因此我们可以预处理以每个点为最大值,左右总长不超过 \(2B+1\) 区间的子区间最大答案,以覆盖所有长小于 \(B\) 的答案。对每次修改,重新计算其周围点的预处理值。

查询时使用另一棵线段树。注意边界条件,应查询 \([l + B, r - B]\) 区间内的预处理值,而对于 \([l, l+B)\)\((r - B, r]\) 区间内的值要根据边界重新计算。

总时间复杂度 \(O((n + q) B^2 + q\dfrac {n\log n} B)\)。空间复杂度 \(O(n)\)

由于大多数点不是局部极大值,预处理很难跑满,因此 \(B\) 可以适当增大,取 \(100\) 左右较优,运行时间约 \(3.3\) 秒。

代码实现

代码的简练程度还算看得过去。

#include <bits/stdc++.h>
#include <atcoder/segtree>
using namespace std;
using i64 = int64_t;
using u64 = uint64_t;

struct Node {
    i64 val, sum;
    Node() : val(0), sum(0) {}
    Node(i64 i, i64 x) : val((x << 18) | i), sum(x) {}
};

Node add(Node a, Node b) {
    Node e;
    e.sum = a.sum + b.sum;
    e.val = max(a.val, b.val);
    return e;
}

Node e() { return {0, 0}; }
using segtree = atcoder::segtree<Node, add, e>;

i64 mx(i64 a, i64 b) { return max(a, b); }
i64 emx() { return 0; }
using segtreemx = atcoder::segtree<i64, mx, emx>;

#define int i64
constexpr int B = 108;
void solve() {
    int n, q;
    cin >> n >> q;
    vector<int> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];
    segtree tr(n);
    for (int i = 0; i < n; i++) tr.set(i, {i, a[i]});
    auto check_bound = [&](int i, int l, int r) {
        int lp, rp;
        int sum = a[i], val = a[i], cnt = 1;
        for (lp = i - 1; lp >= l && a[lp] <= a[i] && i - lp < B; sum += a[lp--], cnt++);
        for (rp = i + 1; rp <= r && a[rp] <= a[i] && rp - i < B; sum += a[rp++], cnt++);
        if (val < (sum + 1) / 2) return cnt;
        return -1ll;
    };
    segtreemx trmx(n);
    for (int i = 0; i < n; i++) trmx.set(i, check_bound(i, 0, n - 1));
    while (q--) {
        int tp; cin >> tp;
        if (tp == 1) {
            int l, r; cin >> l >> r;
            l--, r--;
            int ans = 0;
            for (int i = l; i <= r && i < l + B; i++) ans = max(ans, check_bound(i, l, r));
            for (int i = r; i >= l && i > r - B; i--) ans = max(ans, check_bound(i, l, r));
            if (l + B <= r - B) ans = max(ans, trmx.prod(l + B, r - B + 1));
            function<void(int, int)> dfs = [&](int l, int r) {
                if (r - l + 1 <= B || r - l + 1 <= ans) return;
                auto [v, sum] = tr.prod(l, r + 1);
                int val = v >> 18, pos = v & ((1ll << 18) - 1);
                if (val < (sum + 1) / 2) ans = max(ans, r - l + 1);
                dfs(l, pos - 1), dfs(pos + 1, r);
            };
            dfs(l, r);
            if (ans == 0) ans = -1;
            cout << ans << '\n';
        } else {
            int p, x; cin >> p >> x;
            a[--p] = x; tr.set(p, {p, x});
            for (int i = max(0ll, p - B); i <= min(n - 1, p + B); i++) {
                trmx.set(i, check_bound(i, 0, n - 1));
            }
        }
    }
}
#undef int

int main() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    int t = 1;
    cin >> t;
    while (t--) {
        solve();
    }
}
posted @ 2024-07-23 16:28  cccpchenpi  阅读(41)  评论(0编辑  收藏  举报