首页 > 其他分享 >CF1990F Polygonal Segments 题解

CF1990F Polygonal Segments 题解

时间:2024-07-23 16:29:13浏览次数:16  
标签:i64 int 题解 Segments Polygonal -- 最大值 区间 复杂度

题目链接: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();
    }
}

标签:i64,int,题解,Segments,Polygonal,--,最大值,区间,复杂度
From: https://www.cnblogs.com/cpchenpi/p/18318129

相关文章

  • 题解:P10800 「CZOI-R1」卡牌
    \(\text{Link}\)最近做的最神金的一道数据结构题。题意给出\(m\)个值域为\([1,n]\)的四元组\(t_{i,0\sim3}\),定义四元组\(A\)胜于四元组\(B\)当且仅当最多存在一个\(j\in[0,3]\)使得\(A_j\leB_j\),求出有多少个值域为\([1,n]\)的四元组\(A\)胜于所有的\(t_{1......
  • 题解:P10717「KDOI-05」简单的树上问题
    \(\text{Link}\)题意给你一颗\(n\)个结点的树,有\(k\)次操作,第\(i\)次操作:每个点初始都处于未激活状态;以\(p_{i,j}\)的概率激活点\(j\);对于每个未激活的点\(i\),如果存在激活的结点\(j,k\)且\(i\)在\(j\)到\(k\)的路径上,则\(i\)也会被激活。给出\(v_{i......
  • NOI2024 题解
    D2T3树形图首先判掉一些case将任意一个\(1\)类点定为根,求出一棵dfs树,则图上的非树边只有返祖边,没有横叉边。\(1\)类点考虑在这棵dfs树的基础上求出所有\(1\)类点:考虑\(fa_u\tou\)这条边被几条返祖边覆盖了,由于这是一个强连通分量,所以子树中至少有一条返祖边覆......
  • 题解:CF1992F Valuable Cards
    Part1:前言题目翻译在他最喜欢的咖啡馆里,Kmes再次想尝尝皮草大衣下的鲱鱼。以前,这对他来说并不难,但咖啡馆最近推出了一项新的购买政策。现在,为了进行购买,Kmes需要解决以下问题:在他面前摆放着\(n\)张不同价格的卡,第\(i\)张卡的价格为\(a_i\),在这些价格中没有整数\(x\)。K......
  • 题解:P9437 一棵树
    题目传送门明显的换根dp,感觉是道不错的换根dp练习题。题意一棵\(n\)个节点的树,点带权,定义\(w(x,y)=\overline{a_x\dotsa_y}\)。求\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}w(i,j)\bmod998244353\)。思路我们不妨先求出\(i=1\)时的\(\sumw(i,j)\),再求\(......
  • [SDOI2012] 走迷宫 题解
    [SDOI2012]走迷宫题目描述Morenan被困在了一个迷宫里。迷宫可以视为\(n\)个点\(m\)条边的有向图,其中Morenan处于起点\(s\),迷宫的终点设为\(t\)。可惜的是,Morenan非常的脑小,他只会从一个点出发随机沿着一条从该点出发的有向边,到达另一个点。这样,Morenan走的步数可能......
  • 2024年暑期2024牛客暑期多校训练营1 C和H题解
    C题SumofSuffixSums题目大意:开始是给你一空数组,要经历q次操作,每次操作都会给出两个数字t和v,其中要从数组末尾去走元素t次,最后加上元素v。定义si=ai+ai+1+ai+2+ai+3+......+an,最后求s1+s2+s3+.......+sn的总和。最后答案注意取模。 题解:注意到sum的总和其实就......
  • CF512D Fox And Travelling 题解
    Description给定一张\(n\)个点\(m\)条边的无向图。一个点只有当与它直接相连的点中最多只有一个点未被选择过时才可被选择。询问对于每个\(k\in[0,n]\),有序选择\(k\)个点的方案数。\(n\le100\),\(m\le\frac{n(n-1)}2\),答案对\(10^9+9\)取模。Solution容易发......
  • [COCI2015-2016#1] UZASTOPNI 题解
    前言题目链接:洛谷。题意简述一棵有根树,节点数\(n\leq10^5\),每个点有权值\(v_i\leq2000\),现在选出一些点,满足:一个点的父亲点若未被选择则其不能被选择。所选点的集合内不能有相同的权值。对于每一个选择的点,其子树中所有被选择点的权值必须可以构成公差为\(1\)的等......
  • 2024牛客暑期多校训练营2(部分题目题解)
    2024牛客暑期多校训练营2(部分题目题解)C.RedWalkingonGrid题意:给定只有红白的2*n个格子,只能走红色各自且只能上下左右走,问最多可以走多少红色格子。题解:左右走:dp[0][i]=dp[0][i-1]+1;上下走:intk1=dp[0][i];intk2=dp[1][i];dp[0][i]=max(dp[0][i],k2+......