1 条题解
-
0
题目详解
问题重述
定义数组 的权重为: [ \text{weight}(b) = #{ i \mid b_i > \text{MEX}(b) } ] 其中 是数组 中未出现的最小非负整数。
给定数组 ,对每个位置 ,求所有以 结尾的子数组的最大权重: [ \text{ans}[i] = \max_{1 \le l \le i} w(l, i) ] 其中 是子数组 的权重。
算法思路
关键观察
设子数组的 MEX 值为 ,则权重为子数组中严格大于 的元素个数。
当右端点 固定时,随着左端点 从 向左移动,MEX 值会发生变化,但变化次数有限(MEX 值单调不减,且不超过 )。
我们可以维护一个数据结构,对每个可能的 MEX 值 ,记录当前右端点下 MEX 为 的子数组的最大权重。
动态维护
当右端点从 移动到 ,加入新元素 时:
-
对所有 :
如果 ,则新加入的元素 ,所以权重增加 ;
如果 ,则新元素等于 MEX,不满足严格大于,权重不变。 -
对所有 :
新元素小于 MEX,不影响权重,但 MEX 值可能改变(如果 是之前缺失的,MEX 会变大)。
线段树设计
代码中用线段树维护 表示 MEX 值为 的子数组的最大权重:
- 线段树下标范围 ,对应 MEX 的可能取值。
- 每个节点存储当前区间内所有 的最大值。
- 支持两种操作:
- 区间加:将 范围内的所有 加
- 单点赋值:将位置 的值设为负无穷(表示这些子数组不合法)
为什么这样正确?
-
区间加 :
对所有 ,新元素 是否大于 ?- 若 ,确实 ,应加 ✓
- 若 ,,不应加 ✗
所以我们先统一加 ,再通过单点赋值把 的位置设为负无穷,这样在取最大值时自然排除了这些不合法的子数组。
-
单点赋值负无穷:
表示以当前右端点结尾、MEX 恰好为 的子数组,因为新加入的元素等于 MEX,所以这些子数组的权重不应该被计入。 -
线段树根节点存储最大值:
每次操作后,根节点的值就是 ,即所有以 结尾的子数组的最大权重。
完整代码
#include<bits/stdc++.h> using namespace std; #define all(v) v.begin(),v.end() #define pb push_back #define REP(i,b,e) for(int i=(b);i<(int)(e);++i) int n; int a[300005]; struct ds { int seg[1200005], tag[1200005]; // 建树 void build(int l, int r, int p) { seg[p] = tag[p] = 0; if(l == r) return; int m = (l + r) >> 1; build(l, m, p*2+1); build(m+1, r, p*2+2); } // 区间加 [l, r] void update(int l, int r, int s, int t, int p) { if(l <= s && t <= r) { ++seg[p]; ++tag[p]; return; } int m = (s + t) >> 1; if(m >= l) update(l, r, s, m, p*2+1); if(m < r) update(l, r, m+1, t, p*2+2); seg[p] = max(seg[p*2+1], seg[p*2+2]) + tag[p]; } // 单点赋值负无穷(实际上是 -val,val 是累加标记) void point(int pos, int l, int r, int p, int val = 0) { if(l == r) { seg[p] = -val; // 设为负无穷(实际是负的当前累加值) return; } int m = (l + r) >> 1; val += tag[p]; if(m >= pos) point(pos, l, m, p*2+1, val); else point(pos, m+1, r, p*2+2, val); seg[p] = max(seg[p*2+1], seg[p*2+2]) + tag[p]; } } seg; void Main() { cin >> n; REP(i, 0, n) cin >> a[i]; seg.build(0, n, 0); // 构建线段树 REP(i, 0, n) { // 步骤1:对所有 MEX <= a[i] 的权重加 1 seg.update(0, a[i], 0, n, 0); // 步骤2:将 MEX = a[i] 的权重设为负无穷 seg.point(a[i], 0, n, 0); // 输出当前最大值 cout << seg.seg[0] << ' '; } cout << '\n'; } void TC() { int tc = 1; cin >> tc; while(tc--) { Main(); cout.flush(); } } signed main() { cin.tie(0), cout.tie(0), ios::sync_with_stdio(0); TC(); return 0; }
算法复杂度
- 时间复杂度:,每个元素执行一次区间加和一次单点操作,线段树操作均为 。
- 空间复杂度:,线段树大小约为 。
样例验证
以第二个样例 为例:
i a[i] 操作后根节点值 输出 1 2 max=1 1 2 0 3 max=2 2 4 0 5 1 max=1 1 输出:
1 1 2 2 1,与题目一致。
总结
本题的核心技巧:
- 将问题转化为对每个可能的 MEX 值维护最大权重
- 利用线段树支持区间加和单点修改
- 通过"先加后清空"的技巧处理边界情况
这种方法充分利用了 MEX 值范围有限( 到 )的特点,将原问题优雅地转化为了线段树维护区间最大值的问题。
-
- 1
信息
- ID
- 6637
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者