[ABC470G] ΣШX 题解
题意
给定一个长度为 NNN 的序列 AAA(0≤Ai≤N0 \le A_i \le N0≤Ai≤N),求所有区间的 mex\operatorname{mex}mex(未出现的最小非负整数)。
思路
虽然本题非常典,但由于我没做过所以记录一下。
考虑对于一个右端点 rrr,有多少个 lll 满足 mex(l,r)=k\operatorname{mex}(l,r)=kmex(l,r)=k。设 poskpos_kposk 表示当前 kkk 最后一次出现的位置(没有则为 000),则有 l≥max0≤i≤k−1poskl \ge \max_{\substack{0 \le i \le k-1}}pos_kl≥max0≤i≤k−1posk,即 pospospos 中 k−1k-1k−1 项的前缀最小值。那么对于每一个 kkk,满足 mex(l,r)≥k+1\operatorname{mex}(l,r)\ge k+1mex(l,r)≥k+1 的 lll 的数量即为 pospospos 中 kkk 的前缀最小值。
由此可得,当前所有 kkk 的前缀最小值的和即为当前右端点的贡献。
模拟一下:
A=(2,1,0,3)A=(2,1,0,3)A=(2,1,0,3)。
初始:
| pos | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 0 | 0 | 0 | 0 |
| premin | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 0 | 0 | 0 | 0 |
r=1r=1r=1 时:
| pos | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 0 | 0 | 1 | 0 |
| premin | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 0 | 0 | 0 | 0 |
ans1=0+0+0+0=0ans_1=0+0+0+0=0ans1=0+0+0+0=0。
r=2r=2r=2 时:
| pos | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 0 | 2 | 1 | 0 |
| premin | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 0 | 0 | 0 | 0 |
ans2=0+0+0+0=0ans_2=0+0+0+0=0ans2=0+0+0+0=0。
r=3r=3r=3 时:
| pos | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 3 | 2 | 1 | 0 |
| premin | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 3 | 2 | 1 | 0 |
ans3=3+2+1+0=6ans_3=3+2+1+0=6ans3=3+2+1+0=6。
r=4r=4r=4 时:
| pos | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 3 | 2 | 1 | 4 |
| premin | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| - | 3 | 2 | 1 | 1 |
ans4=3+2+1+1=7ans_4=3+2+1+1=7ans4=3+2+1+1=7。
最终答案即为 ans=0+0+6+7=13ans=0+0+6+7=13ans=0+0+6+7=13。
如果暴力维护 preminpreminpremin 显然是 O(n2)O(n^2)O(n2) 的,需要优化。
考虑线段树维护区间前缀最大值和,难点在于如何合并。
想要将左区间和右区间合并,首先左区间的贡献是不变的,考虑右区间的贡献。

图中红色部分是有用的贡献,蓝色是无用的贡献,则右区间的贡献相当于在前面加一个左区间最小值时的答案。
设 findc(p,v)findc(p,v)findc(p,v) 为当前在线段树上的 ppp 节点,表示的区间为 [lp,rp][l_p,r_p][lp,rp],前面有一个数 vvv 时的贡献。设 lc(p),rc(p)lc(p),rc(p)lc(p),rc(p) 是 ppp 的左右节点,mnp,valpmn_p,val_pmnp,valp 分别表示节点所表示的区间的最小值以及答案。
当 mnlc(p)<vmn_{lc(p)}<vmnlc(p)<v 时说明 vvv 可能会对 vallc(p)val_{lc(p)}vallc(p) 产生影响,但不会对 valrc(p)val_{rc(p)}valrc(p) 产生影响(注:此处的 valrc(p)val_{rc(p)}valrc(p) 是合并后的,已经算了 lc(p)lc(p)lc(p) 的影响,在代码中需要写成 valp−vallc(p)val_p-val_{lc(p)}valp−vallc(p)),即 findc(p,v)←findc(lc(p),c)+valrc(p)findc(p,v) \leftarrow findc(lc(p),c)+val_{rc(p)}findc(p,v)←findc(lc(p),c)+valrc(p)。
否则 vvv 会将 vallc(p)val_{lc(p)}vallc(p) 覆盖为 (rlc(p)−llc(p)+1)×v(r_{lc(p)}-l_{lc(p)}+1)\times v(rlc(p)−llc(p)+1)×v,会对右区间产生影响,所以 findc(p,v)←(rlc(p)−llc(p)+1)×v+findc(rc(p),c)findc(p,v) \leftarrow (r_{lc(p)}-l_{lc(p)}+1)\times v+findc(rc(p),c)findc(p,v)←(rlc(p)−llc(p)+1)×v+findc(rc(p),c)。
最后特判一下叶子节点即可。
findc(p,v)={min(valp,v)p is a leaf nodefindc(lc(p),c)+valrc(p)mnlc(p)<v(rlc(p)−llc(p)+1)×v+findc(rc(p),c)other findc(p,v)= \begin{cases} \min(val_p,v) & p\ is\ a\ leaf\ node\\ findc(lc(p),c)+val_{rc(p)} & mn_{lc(p)}<v\\ (r_{lc(p)}-l_{lc(p)}+1)\times v+findc(rc(p),c) & other \end{cases} findc(p,v)=⎩⎨⎧min(valp,v)findc(lc(p),c)+valrc(p)(rlc(p)−llc(p)+1)×v+findc(rc(p),c)p is a leaf nodemnlc(p)<vother
findcfindcfindc 的时间复杂度为 O(logn)O(\log n)O(logn),即一次 pushuppushuppushup 的时间复杂度为 O(logn)O(\log n)O(logn),修改一个值的时间复杂度为 O(log2n)O(\log^2 n)O(log2n),一共 NNN 个元素,总时间复杂度为 O(nlog2n)O(n \log^2 n)O(nlog2n)。
代码
#include<bits/stdc++.h>
#define lc(p) ((p)<<1)
#define rc(p) ((p)<<1|1)
using namespace std;
typedef long long ll;
int n,a[300010];
struct N{
ll mn,c;
}tr[1200010];
ll findc(int p,int l,int r,ll v){
if(l==r)return min(tr[p].mn,v);
int mid=(l+r)>>1;
if(tr[lc(p)].mn<v)return findc(lc(p),l,mid,v)+tr[p].c-tr[lc(p)].c;
else return (mid-l+1)*v+findc(rc(p),mid+1,r,v);
}
void pushup(int p,int l,int r){
int mid=(l+r)>>1;
tr[p].c=tr[lc(p)].c+findc(rc(p),mid+1,r,tr[lc(p)].mn);
tr[p].mn=min(tr[lc(p)].mn,tr[rc(p)].mn);
}
void change(int p,int l,int r,int x,int v){
if(l==r){
tr[p].mn=tr[p].c=v;
return ;
}
int mid=(l+r)>>1;
if(x<=mid)change(lc(p),l,mid,x,v);
else change(rc(p),mid+1,r,x,v);
pushup(p,l,r);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
ll ans=0;
for(int i=1;i<=n;i++){
cin>>a[i];
change(1,0,n,a[i],i);
ans+=tr[1].c;
}
cout<<ans;
return 0;
}

310

被折叠的 条评论
为什么被折叠?



