啊这,这题洛谷居然有原题 Array | HDU7020 | 杭电多校五 09题 赛内想了很久,各种算奇怪的前缀和之类的 最后调完了已经赛后半小时了,但是
T
L
E
TLE
TLE 了 交了洛谷,发现洛谷是
7
s
7s
7s 然后经过了各种卡常技巧的优化,特别是最后加了
i
n
l
i
n
e
inline
inline 直接从
>
8000
M
s
>8000Ms
>8000Ms 卡进
7000
+
M
s
7000+Ms
7000+Ms 还有
O
2
O2
O2 加上,变成
6300
M
s
6300Ms
6300Ms 了…
题意
Array | HDU7020 | 杭电多校五 09题 给定一个长度为
N
N
N 的序列
A
[
N
]
A[N]
A[N] 求有多少个连续子序列
[
L
,
R
]
[L,R]
[L,R],满足这个子序列中只有一个众数 众数:序列中出现次数最多的数
1
≤
N
≤
1
0
6
1\le N\le 10^6
1≤N≤106
0
≤
A
[
i
]
≤
1
0
6
0\le A[i]\le 10^6
0≤A[i]≤106
假设我们现在考虑数字
a
a
a ,假设其他非
a
a
a 的数字,我们记作
b
b
b ,于是变成了一个
a
b
ab
ab 串 因为要求区间的众数,很套路的,目前位置为
i
i
i ,我们记数字
a
a
a 的前缀和为
p
[
i
]
p[i]
p[i] 于是合法区间就变成了如下式子:
p
[
R
]
−
p
[
L
−
1
]
>
R
−
L
+
1
2
p[R]-p[L-1]>\frac{R-L+1}{2}
p[R]−p[L−1]>2R−L+1 可能我们会去从这个角度出发,转化式子为:
p
[
R
]
−
R
2
>
p
[
L
−
1
]
−
L
−
1
2
p[R]-\frac{R}{2}>p[L-1]-\frac{L-1}{2}
p[R]−2R>p[L−1]−2L−1 然后我们线段树记录
p
[
i
]
−
i
2
p[i]-\frac{i}{2}
p[i]−2i ,去区间查询? 然后发现
这
样
子
没
法
做
\color{red}{这样子没法做}
这样子没法做 这里我们必须固定区间右端点
R
R
R ,对于数字
a
a
a 才能算出有多少满足要求的左端点
L
L
L 但是我们可能有
n
n
n 个不同的数字,每个数字的右端点都是无法固定的,都要暴力枚举过去 也就是时间复杂度变成了
O
(
n
2
log
n
)
O(n^2\log n)
O(n2logn),难定,比暴力还慢
深入尝试
然后发现,如果我们去记录每个位置的值 是一定做不了的,我们就只能记录 每个值出现的次数 去做 启发:摩尔投票法 我们希望算出
[
1
,
n
]
[1,n]
[1,n] 的众数,我们去记录一个栈 如果
A
[
i
]
A[i]
A[i] 等于栈顶的元素,或者栈为空,那么入栈一个
A
[
i
]
A[i]
A[i] 否则,出栈一个 这样,最后的栈顶元素就是区间的众数
考虑类似的做法。我们现在考虑数字
a
a
a 如果
A
[
i
]
=
a
A[i]=a
A[i]=a ,那么我们记
p
r
e
[
i
]
=
p
r
e
[
i
−
1
]
+
1
pre[i]=pre[i-1]+1
pre[i]=pre[i−1]+1 否则,
p
r
e
[
i
]
=
p
r
e
[
i
−
1
]
−
1
pre[i]=pre[i-1]-1
pre[i]=pre[i−1]−1 (注意,这里
p
r
e
pre
pre 数组并不是前缀和) 举例:
A
[
]
=
a
,
b
,
a
,
b
,
b
,
b
,
a
,
a
,
b
p
r
e
[
]
=
1
,
0
,
1
,
0
,
−
1
,
−
2
,
−
1
,
0
,
−
1
\begin{aligned} A[]=&a,b,a,b,b,b,a,a,b\\ pre[]=&1,0,1,0,-1,-2,-1,0,-1 \end{aligned}
A[]=pre[]=a,b,a,b,b,b,a,a,b1,0,1,0,−1,−2,−1,0,−1 考虑到,我们固定右端点为
R
R
R 那么所有满足要求的左端点
L
L
L,都满足:
p
r
e
[
R
]
−
p
r
e
[
L
−
1
]
>
0
pre[R]-pre[L-1]>0
pre[R]−pre[L−1]>0 等价于:
p
r
e
[
R
]
≥
p
r
e
[
L
−
1
]
−
1
pre[R]\ge pre[L-1]-1
pre[R]≥pre[L−1]−1
但是刚刚说了,我们不能记录位置,而是去记录值的数量,所以我们改成权值线段树:
p
r
e
[
R
]
≥
p
r
e
[
−
I
N
F
]
∼
p
r
e
[
R
−
1
]
pre\Big[R\Big]\ge pre\Big[-INF\Big] \sim pre\Big[R-1\Big]
pre[R]≥pre[−INF]∼pre[R−1] 可以用线段树区间查询去获取有多少满足的数量
∑
(
p
r
e
[
−
I
N
F
]
∼
p
r
e
[
R
−
1
]
)
\sum\Big( pre\Big[-INF\Big] \sim pre\Big[R-1\Big] \Big)
∑(pre[−INF]∼pre[R−1])
考虑更新: 对于新的位置,我们只有
p
r
e
[
R
]
pre\Big[R\Big]
pre[R] 增加
1
1
1,其他都不变 所以我们可以单点更新,十分简单。
但是刚刚也说了,我们如果对于所有的
R
R
R 全去做一遍,就变成了
O
(
N
2
log
N
)
O(N^2\log N)
O(N2logN) 了,考虑优化 首先,如果当前位置
A
[
i
]
=
a
A[i]=a
A[i]=a,我们当然要固定右端点
R
=
i
R=i
R=i 然后去做 但是如果当前位置
A
[
i
]
=
b
A[i]=b
A[i]=b,我们可能也要去固定右端点
R
=
i
R=i
R=i 然后去做 比如
A
[
]
=
a
a
b
b
A[]=aabb
A[]=aabb
R
=
3
R=3
R=3,当然有合法的:
[
a
a
b
]
[aab]
[aab] 但是
R
=
4
R=4
R=4 就没有合法的区间了 这个时候问题来了:哪些位置我们需要去固定当前位置作为右端点去查询? 一个很简单的想法就是:当当前位置作为右端点,且有满足的左端点,满足区间的众数为
a
a
a 的嘛! 也就是满足
p
r
e
[
R
]
≥
p
r
e
[
−
I
N
F
]
∼
p
r
e
[
R
−
1
]
,
∃
p
r
e
[
k
]
≠
0
pre\Big[R\Big]\ge pre\Big[-INF\Big] \sim pre\Big[R-1\Big]\quad ,\exist pre\Big[k\Big]\ne 0
pre[R]≥pre[−INF]∼pre[R−1],∃pre[k]=0 我们只需要记录最小的
p
r
e
[
k
]
=
m
i
n
pre\Big[k\Big]=min
pre[k]=min ,看是否满足
p
r
e
[
R
]
≥
m
i
n
pre\Big[R\Big]\ge min
pre[R]≥min 即可 举例:
A
[
]
=
a
,
a
,
b
,
b
,
b
,
b
,
b
,
b
,
a
p
r
e
[
]
=
1
,
2
,
1
,
0
,
−
1
,
−
2
,
−
3
,
−
4
,
−
3
A[]=a,a,b,b,b,b,b,b,a\\ pre[]=1,2,1,0,-1,-2,-3,-4,-3
A[]=a,a,b,b,b,b,b,b,apre[]=1,2,1,0,−1,−2,−3,−4,−3 注意到当某个
b
b
b 的位置
i
i
i 满足
p
r
e
[
i
]
<
0
pre[i]<0
pre[i]<0 时,就没有满足要求的了,这些点都不用作为右端点去获取值
还有我们注意到,我们不能对于每个值
a
a
a,对于每个位置
i
i
i 我们都去单点更新
p
r
e
[
]
pre[]
pre[] 数组 考虑到上面中间一连串
b
b
b 的位置,我们的
p
r
e
[
]
pre[]
pre[] 数组中,数字从
2
2
2 降到
−
4
-4
−4 所以我们只需要区间更新
u
p
d
a
t
e
[
−
4
,
2
]
update[-4,2]
update[−4,2] 区间,赋值
+
1
+1
+1 即可快速记录所有的
p
r
e
[
A
[
]
]
pre[A[]]
pre[A[]] 的值
根据摊还分析,
a
a
a 使势能提高
1
1
1,
b
b
b 使势能降低
1
1
1 ,我们
u
p
d
a
t
e
update
update 的次数自然是
2
×
c
n
t
a
2\times cnt_a
2×cnta 因为只有当 势能
≥
m
i
n
\ge min
≥min 的时候我们才去更新
卡常小技巧
思路差不多到这里,但是写着时间复杂度仍然会非常爆炸 首先,最多有
n
n
n 个不同的数字,因为我们记录的是
p
r
e
[
R
]
pre\Big[R\Big]
pre[R] ,自然会有负数,我们增加一个
O
R
I
ORI
ORI 偏移常量 对于每一个不同的数字,我们的
p
r
e
[
]
pre[]
pre[] 数字都要清空,这样貌似清空的总体复杂度就是
O
(
n
2
)
O(n^2)
O(n2) 了?
简单优化:我们使用
o
p
[
i
]
op[i]
op[i] 去记录第
i
i
i 个更新的操作区间为
[
L
i
,
R
i
]
[L_i,R_i]
[Li,Ri],操作完某个数之后我们对于每一个更新的区间全都反向赋值,原来
+
1
+1
+1 赋值变成
−
1
-1
−1 这样清空复杂度:
O
(
n
2
)
→
O
(
n
k
log
n
)
O(n^2)\rightarrow O(nk\log n)
O(n2)→O(nklogn) 但是仍然很慢
深度优化: 我们使用乘法线段树,即多做一个清空标记,即若想清空复杂度,就区间
∗
0
*0
∗0 ,只需要一次
u
p
d
a
t
e
update
update 就好了,快很多:
O
(
n
2
)
→
O
(
n
log
n
)
O(n^2)\rightarrow O(n\log n)
O(n2)→O(nlogn)
致命优化: 线段树我们加上
i
n
l
i
n
e
inline
inline ,直接洛谷快
1
s
1s
1s ,
H
D
U
HDU
HDU 从
T
L
E
→
A
C
\color{red}{TLE}\rightarrow \color{green}{AC}
TLE→AC 或者加上 #pragma GCC optimize(2) ,也可以快很多 对了,应该没有人不加快读的吧
代码
时间复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn)
#pragmaGCC optimize(2)#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}#definels(p<<1)#definers(p<<1|1)#definemd((l+r)>>1)#definelllonglongconstint MAX =2e6+50;int tree[MAX*4];int tadd[MAX*4];
bool tmul[MAX*4];inlinevoidpush_up(int p){
tree[p]=tree[ls]+tree[rs];}inlinevoidadd(int p,int l,int r,int add,bool mul){if(mul){
tree[p]= tree[p]+ add *(r - l +1);
tmul[p]= tmul[p];
tadd[p]= tadd[p]+ add;}else{
tree[p]= add *(r - l +1);
tmul[p]=0;
tadd[p]= add;}}inlinevoidpush_down(int p,int l,int r){add(ls,l,md,tadd[p],tmul[p]);add(rs,md+1,r,tadd[p],tmul[p]);
tadd[p]=0;
tmul[p]=1;}inlinevoidupdateMul(int p,int l,int r,int ux,int uy,bool k){if(ux <= l && uy >= r){add(p,l,r,0,k);return;}push_down(p,l,r);if(ux <= md)updateMul(ls,l,md,ux,uy,k);if(uy > md)updateMul(rs,md+1,r,ux,uy,k);push_up(p);}inlinevoidupdateAdd(int p,int l,int r,int ux,int uy,int k){if(ux <= l && uy >= r){add(p,l,r,k,1);return;}push_down(p,l,r);if(ux <= md)updateAdd(ls,l,md,ux,uy,k);if(uy > md)updateAdd(rs,md+1,r,ux,uy,k);push_up(p);}inlineintquery(int p,int l,int r,int qx,int qy){int res =0;if(qx <= l && r <= qy)return tree[p];push_down(p,l,r);if(qx <= md)res +=query(ls,l,md,qx,qy);if(qy > md)res +=query(rs,md+1,r,qx,qy);return res;}int nxt[MAX];int las[MAX];
bool M[MAX];int hd[MAX];constint YOU =2e6+10;constint ORI =1e6+5;int val[MAX];intmain(){int T;T =read();//T = 1;updateAdd(1,0,YOU,ORI,ORI,1);while(T--){int n;n =read();//int tra;tra = read();int cnt =0;for(int i =1;i <= n;++i){
val[i]=read();if(!M[val[i]]){
hd[++cnt]= i;
M[val[i]]=1;
las[val[i]]=0;}
nxt[las[val[i]]]= i;
las[val[i]]= i;
nxt[i]= n +1;}
ll ans =0;for(int i =1;i <= cnt;++i){int pos =1;int shu =0;if(pos < hd[i]){int x = hd[i]-1;updateAdd(1,0,YOU,-x+ORI,-1+ORI,1);
shu -= x;}
pos = hd[i];int mn =min(shu,0);while(pos <= n){
shu++;updateAdd(1,0,YOU,shu+ORI,shu+ORI,1);
ans = ans +(ll)query(1,0,YOU,mn,shu+ORI-1);if(pos == n)break;if(nxt[pos]== pos+1){
pos ++;continue;}int x = nxt[pos]- pos -1;int xia = nxt[pos];int tmp = shu;for(int j = pos +1;j <= xia -1;++j){
shu--;if(shu > mn){
ans = ans +(ll)query(1,0,YOU,mn,shu+ORI-1);}else{break;}}updateAdd(1,0,YOU,tmp-x+ORI,tmp-1+ORI,1);
pos = nxt[pos];
shu = tmp - x;
mn =min(mn,shu);}updateMul(1,0,YOU,0,YOU -1,0);updateAdd(1,0,YOU,ORI,ORI,1);}for(int i =1;i <= n;++i)
M[val[i]]=0;
cout << ans << endl;}return0;}