题目描述
难度分:2400
输入n(1≤n≤105)和长为n的数组a(1≤a[i]≤n)。
定义一个数组的mex为不在这个数组中的最小正整数。
把a的所有非空连续子数组的mex丢到一个数组b中,输出b的mex。
输入样例1
3
1 3 2
输出样例1
3
输入样例2
5
1 4 3 1 2
输出样例2
6
算法
思维+线段树
把所有可能的子数组mex算出来,就可以算出答案,但是在这个数据规模下我们肯定不能暴力去算。
这个题第一步就很有思维难度:要检查是否有子数组的mex等于6,我们可以把a按照6切分成若干段,检查是否有一段包含了[1,5]中的所有整数。如果有,那么这一段的mex就是6。
我们可以在遍历a的同时,维护每个a[i]最近一次出现的下标last[a[i]],用一棵权值线段树维护元素的最小下标。对于a[i],用线段树查询出[1,a[i]−1]每个数的最近一次出现下标的最小值minIdx,如果minIdx>last[a[i]],说明这前一个a[i]到i位置的这个a[i]之间,包含了区间[1,a[i]−1]中的所有整数,那么这一段子数组的mex=a[i]。将线段树更新a[i]的位置到i,继续遍历下一个元素。
其它情况:
- 如果a中有1,那么子数组的mex可以等于2。
- 如果a中有大于1的数,那么子数组的mex可以等于1。
对于i∈[2,n+1],利用权值线段树检查数值[1,i)的下标最小值是否超过last[i],超过就说明有子数组满足mex=i,做个标记(可以用一个st数组标记st[i]=true
)。最后遍历正数[1,n+1],找到第一个未被标记(即st[i]=false
)的i就是答案。
复杂度分析
时间复杂度
遍历数组a的时间复杂度为O(n),遍历的同时一边维护last数组和线段树。而对于每个a[i],操作性能的瓶颈在于线段树操作,时间复杂度为O(log2n)。因此,这一步的时间复杂度为O(nlog2n)。
遍历i∈[2,n+1]填标记数组时对于每个i也需要用线段树进行查询,因此时间复杂度为O(nlog2n)。最后遍历标记数组确定最终答案的时间复杂度为O(n),并不是算法的瓶颈。
综上,整个算法的时间复杂度为O(nlog2n)。
空间复杂度
空间消耗主要在于标记数组st,每个数值最后一次出现的位置数组last,以及权值线段树。它们的消耗都是线性的,因此算法的额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, a[N], last[N];
bool st[N];
class SegmentTree {
public:
int a[N];
struct Info {
int l, r, mn;
Info() {}
Info(int left, int right, int v): l(left), r(right), mn(v) {}
} seg[N<<2];
explicit SegmentTree() {}
void build(int u, int l, int r) {
if(l == r) {
seg[u] = Info(l, r, 0);
}else {
int mid = l + r >> 1;
build(u<<1, l, mid);
build(u<<1|1, mid + 1, r);
pushup(u);
}
}
void modify(int pos, int val) {
modify(1, pos, val);
}
Info query(int l, int r) {
if(l > r) return Info(0, 0, 0);
return query(1, l, r);
}
private:
void modify(int u, int pos, int val) {
if(seg[u].l == pos && seg[u].r == pos) {
seg[u] = Info(pos, pos, seg[u].mn + val);
}else {
int mid = seg[u].l + seg[u].r >> 1;
if(pos <= mid) modify(u<<1, pos, val);
else modify(u<<1|1, pos, val);
pushup(u);
}
}
Info query(int u, int l, int r) {
if(l <= seg[u].l && seg[u].r <= r) return seg[u];
int mid = seg[u].l + seg[u].r >> 1;
if(r <= mid) {
return query(u<<1, l, r);
}else if(mid < l) {
return query(u<<1|1, l, r);
}else {
return merge(query(u<<1, l, r), query(u<<1|1, l, r));
}
}
void pushup(int u) {
seg[u] = merge(seg[u<<1], seg[u<<1|1]);
}
Info merge(const Info& lchild, const Info& rchild) {
Info info;
info.l = lchild.l;
info.r = rchild.r;
info.mn = min(lchild.mn, rchild.mn);
return info;
}
};
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
SegmentTree seg;
seg.build(1, 1, n);
for(int i = 1; i <= n; i++){
if(a[i] != 1) {
st[1] = true;
}
if(a[i] > 1 && seg.query(1, a[i] - 1).mn > last[a[i]]) {
st[a[i]] = true;
}
last[a[i]] = i;
seg.modify(a[i], i - seg.query(a[i], a[i]).mn); // a[i]的位置改成i
}
for(int i = 2; i <= n + 1; i++) {
if(seg.query(1, i - 1).mn > last[i]) {
st[i] = true;
}
}
int ans = 1;
while(ans <= n + 1 && st[ans]) {
ans++;
}
printf("%d\n", ans);
return 0;
}