题意:
给定一个 N N N,求所有 N N N 的子区间 [ l , r ] , 1 ≤ l ≤ r ≤ N [l,r],1\leq l\leq r\leq N [l,r],1≤l≤r≤N 中满足 i ∈ [ l , r ] i \in [l,r] i∈[l,r] 中有至少一个 A i A_i Ai 的出现次数有且仅出现一次。
题意很明确,如何解决?
暴力:
直接 N 2 N^2 N2 扫一遍然后进行每个区间的特判即可,复杂度 O ( N 3 ) O(N^3) O(N3) 估计只能过样例。
莫队:
由于发现对于一个区间 [ l , r ] [l,r] [l,r] 只要其中一个 i ∈ [ l , r ] i \in [l,r] i∈[l,r] 中的 c o u n t ( A i ) count(A_i) count(Ai) 仅仅出现一次且拓展的 [ l , r ] [l,r] [l,r] 如果不等于 A i A_i Ai 那么这个拓展的区间也是合法的区间。故可以用莫队做,但是可能会很麻烦。具体的:
inline void upd(int x){
mp[a[x]]--;
}
inline bool que(int x){
if(mp[a[x]]>=1){
return false;
}
return true;
}
这样做是 O ( N 2 N ) O(N^2\sqrt N) O(N2N) 的反正也过不了。
正解:
考虑用
L
i
L_i
Li 表示
i
i
i 最左出现的位置,
R
i
R_i
Ri 表示
i
i
i 最右边出现的位置,那么对于一个
i
i
i 肯定满足
[
L
A
i
,
i
]
[L_{A_i},i]
[LAi,i] 满足和
[
i
,
R
A
i
]
[i,R_{A_i}]
[i,RAi] 满足,并且
[
L
A
i
−
1
,
.
.
.
.
.
k
<
R
]
[L_{A_i}-1,.....k<R]
[LAi−1,.....k<R] 都满足,同理右端点也一样,所以由乘法原理得所有满足的区间个数有
(
R
i
−
i
+
1
)
(
i
−
L
i
+
1
)
(R_i-i+1)(i-L_i+1)
(Ri−i+1)(i−Li+1) 个。
但是这样做会发现有很多数会重复计算,所有可以用扫描线算法每个
i
i
i 对应了左上角为
(
L
i
,
R
i
)
(L_i,R_i)
(Li,Ri) 右下角为
(
i
,
i
)
(i,i)
(i,i) 的矩形来计算重合的部分,(只不过是一维的)也可以用二维线段树的区间赋值,于是答案即为重合部分的面积,复杂度为
O
(
N
log
2
N
)
O(N\log_2 N)
O(Nlog2N) 可以过的。
具体的:
设
p
o
s
i
,
j
,
k
pos_{i,j,k}
posi,j,k 表示
i
i
i 在
j
j
j 后面(也就是右面) 所出现的第
k
k
k 次的位置。
那么有形如双指针的方法枚举左端点
l
l
l 考虑
r
r
r 的取值,即-右端点
r
r
r 应该满足的条件,显然,当有一个数
x
x
x 时,左端点为
l
l
l 则右端点为
[
p
o
s
x
,
l
,
1
,
p
o
s
x
,
l
,
2
)
[pos_{x,l,1},pos_{x,l,2})
[posx,l,1,posx,l,2) 这里
p
o
s
x
,
l
,
2
pos_{x,l,2}
posx,l,2 不能取,因为如果取就会出现两个
x
x
x。
我们要在每次
l
l
l 往右都有且仅有一个值给
l
l
l 这个值就是
A
l
A_l
Al 那么会给
l
l
l 所贡献的区间会从
[
l
,
p
o
s
l
,
a
l
,
2
)
[l,pos_{l,a_l,2})
[l,posl,al,2) 同理,往后的区间会是
[
p
o
s
l
,
a
l
,
2
,
p
o
s
l
,
a
l
,
3
)
[pos_{l,a_l,2},pos_{l,a_l,3})
[posl,al,2,posl,al,3) 其实就是把这个区间向后移了一位
然后发现这个并集大小的维护实际上是要我们支持区间加 / 减查询区间非 0
数个数,同时保证任意时刻所有数非负。于是直接把扫描线维护矩形面积并的线段树拿过来即可。具体地,维护区间内最小值及其个数,查询时若区间最小值为 0
,则返回区间长度减去最小值个数,否则返回区间长度。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,a[N],l[N],r[N];
int mp[N];
long long ans=0;
struct edge{
int ll,rr,hh,ff;
}e[N<<1];
int cnt;
inline int ls(int p){
return p<<1;
}
inline int rs(int p){
return p<<1|1;
}
void add(int l,int r,int h,int f){
e[++cnt].ll=l;
e[cnt].rr=r;
e[cnt].hh=h;
e[cnt].ff=f;
}
bool operator <(const edge &a,const edge &b){
if(a.hh==b.hh)return a.ff>b.ff;
return a.hh<b.hh;
}
struct node{
int sum,len;
}tr[N<<2];
void pushup(int p,int l,int r){
if(tr[p].sum) tr[p].len=r-l+1;
else if(l==r) tr[p].len=0;
else tr[p].len=tr[ls(p)].len+tr[rs(p)].len;
}
void update(int p,int l,int r,int L,int R,int val){
if(L<=l&&r<=R){
tr[p].sum+=val;
pushup(p,l,r);
return ;
}
int mid=(l+r)>>1;
if(L<=mid)update(ls(p),l,mid,L,R,val);
if(R>mid) update(rs(p),mid+1,r,L,R,val);
pushup(p,l,r);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
l[i]=mp[a[i]]+1;
mp[a[i]]=i;
}
for(int i=1;i<N;i++) mp[i]=0;
for(int i=n;i>=1;i--){
if(mp[a[i]])r[i]=mp[a[i]]-1;
else r[i]=n;
mp[a[i]]=i;
}
for(int i=1;i<=n;i++){
add(i,r[i]+1,l[i],1);
add(i,r[i]+1,i+1,-1);
}
sort(e+1,e+1+cnt);
for(int i=1;i<cnt;i++){
update(1,1,n+1,e[i].ll,e[i].rr-1,e[i].ff);
ans+=1ll*tr[1].len*(e[i+1].hh-e[i].hh);
}
cout<<ans;
return 0;
}