题目
题目背景
“你见过我的全盛时期吗?”
D
D
(
X
Y
X
)
\sf DD(XYX)
DD(XYX) 冷笑着,一脚将
O
n
e
I
n
D
a
r
k
\sf OneInDark
OneInDark 踢飞,“你也想起舞吗?”
“能阻止我的生物只有一个——千手嘟间!就凭这个——”
“完全体丶须佐科夫!”
题目描述
给出一个括号序列,每次询问一个子区间
[
l
,
r
]
[l,r]
[l,r]:请在其中找出最长的子区间构成合法括号序列。
数据范围与提示
n
⩽
1
0
5
n\leqslant 10^5
n⩽105,但 那挨千刀的坏东西出题人设置
m
⩽
4
×
1
0
6
m\leqslant 4\times 10^6
m⩽4×106 。时限
2
s
2\rm s
2s,空间限制
512
M
512\rm M
512M 。
思路
我的做法也是 O ( m log n ) \mathcal O(m\log n) O(mlogn),但是常数大,所以过不了。简单来讲,猫树。
并非所有区间询问都需要线段树 / / / 猫树。哪怕渐进时间复杂度是对的,常数也不会允许……空间复杂度、代码复杂度则劣多了……
基本题意转化是,规定 (
为
−
1
-1
−1 而 )
为
+
1
+1
+1,求前缀和,则目标为找到
s
l
=
s
r
s_l=s_r
sl=sr 使得
∀
k
∈
(
l
,
r
)
,
s
k
<
s
l
\forall k\in(l,r),\;s_k<s_l
∀k∈(l,r),sk<sl,最大化
(
r
−
l
)
(r-l)
(r−l) 。
注意到 s i s_i si 的变化是 ± 1 \pm 1 ±1,所以其实 只需找出前缀 max \max max 和后缀 max \max max 。因为前缀 max \max max 相差 1 1 1,所以二者构成的左闭右开区间必然可行,比其更小的不用再找,不存在比其更大的。
想到上面这个东西的原因是,考虑最简单的做法,离线后扫描右端点,维护左端点对应答案。你会发现是单调栈,也就是找出上一个比自己大的值,也就是后缀 max \max max 。
本题中最舒服的是, max \max max 之间完全不必考虑。所以直接在单调栈上求贡献即可。
再考虑一下发现可以在线,反正单调栈的结构是固定的树形结构。倍增。时间复杂度 O ( m log n ) \mathcal O(m\log n) O(mlogn),空间复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) 。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, f = 1, c = getchar();
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(const int &x){
if(x > 9) writeint(x/10);
putchar((x%10)^48);
}
inline void getMax(int &x, const int &y){
if(x < y) x = y;
}
const int MAXN = 100005, LOGN = 18;
int lfa[MAXN][LOGN], lv[MAXN][LOGN];
int rfa[MAXN][LOGN], rv[MAXN][LOGN];
int a[MAXN], sta[MAXN], top;
char str[MAXN];
int main(){
int n = readint(), m = readint();
scanf("%s",str+2);
rep(i,2,n+1){
if(str[i] == 'F') a[i] = a[i-1]-1;
else a[i] = a[i-1]+1; // ')'
}
rep(i,1,n+1){
while(top && a[sta[top]] <= a[i]) -- top;
lfa[i][0] = sta[top], lv[i][0] = i-sta[top]-1;
for(int j=0; lfa[i][j]; ++j){
lfa[i][j+1] = lfa[lfa[i][j]][j];
lv[i][j+1] = max(lv[i][j],lv[lfa[i][j]][j]);
}
sta[++ top] = i;
}
drep(i,n+1+(top=0),1){
while(top && a[sta[top]] <= a[i]) -- top;
rfa[i][0] = sta[top], rv[i][0] = sta[top]-i-1;
for(int j=0; rfa[i][j]; ++j){
rfa[i][j+1] = rfa[rfa[i][j]][j];
rv[i][j+1] = max(rv[i][j],rv[rfa[i][j]][j]);
}
sta[++ top] = i;
}
for(int l,r,x,y,res=0; m; --m,res=0){
l = y = readint(), r = x = readint()+1;
for(int j=LOGN-1; ~j; --j){
if(lfa[x][j] && lfa[x][j] >= l-1){
res = max(res,lv[x][j]);
x = lfa[x][j]; // jumping
}
if(rfa[y][j] && rfa[y][j] <= r+1){
res = max(res,rv[y][j]);
y = rfa[y][j];
}
}
res = std::max(res,x-y);
writeint(res), putchar('\n');
}
return 0;
}