题目链接:https://codeforces.com/contest/1990/problem/F
赛时想到了一个略显抽象的做法,但因为写反了一个判断导致没能过掉。赛后调参卡过,用时 \(3.5/8\) 秒。为了不丢失这个 idea 最终还是决定写个题解记录一下。
题意简述
给定一个数组 \(a_{1..n}\),执行以下查询:
-
查询区间 \([l, r]\) 内,最长的“多边形区间”子区间。一个区间为多边形区间,当且仅当其中的数可以成为一个多边形的边长。
-
将 \(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