【题解】P8037 [COCI2015-2016#7] Prokletnik
提供一种树状数组的解法。
题目链接。
题意简述
一个好区间定义为:这个区间的两个端点值为整个区间的最大值和最小值。
有一段长为 \(n(1 \le n \le 500000)\)的序列,现在提出 \(Q\) 个询问 \((Q \le 500000)\) 每次有 \(L,R(1 \le L \le R \le N)\)表示询问的区间。
求出 \([L,R]\) 中最长的子区间,是好区间。
题目分析
套路地,我们可以离线,按照右端点排序。那么类似增量法,每次加入一个 \(r\),维护答案并查询。
一开始的想法:单调栈预处理出向左 / 向右第一个大于 / 小于的位置,然后二维偏序,对于当前的 \(r\) 每个 \(i\) 维护的是 \([i,r]\) 这个区间的答案。但不知道怎么继续,难点在于每一个偏序找到的点都要大范围地更新答案。
首先令右端点的值为最大值,相反的将每个 \(a_i\) 取反再跑一遍即可。我们维护一个单调递减的栈,表示这些可以被 \(i\) 以及后续更新到的位置。和其他题解中的方式基本相同,但我们先思考线段树到底是怎么维护的。是不是对于栈中的点的存在与否进行 \(pos\) 标记(即修改),再以打 \(tag\) 标记的方式去将答案放到线段树的节点上。既然是以栈中元素作为中心,为什么不直接开一个树状数组去维护栈中元素?
具体实现如下:
- 维护一个树状数组 \(R\),表示对应的栈中的元素作为左端点时的最大右端点,维护前缀最大值。
- 维护一个树状数组 \(ans\),表示 \(i\) 节点为左端点的最大答案,维护后缀最大值。
- 每一次要加入当前的 \(a_i\),将栈中不再会被更新的元素弹掉,并将其答案
R.query(top)-st[top]+1加入到 \(ans\) 中。 - 然后将 \(i\) 入栈,二分找到栈中的第一个合法位置,并修改 \(R\) 和 \(ans\)。由于 \(R\) 维护的是前缀最值,修改是改其后面的部分,故栈中所有要被更新的左端点都被更新了。
- 处理询问时,令栈中第一个大于等于当前询问的位置为 \(it\)。由于我们先前更新时对于 \(R\) 的更新时没有全部都更新到 \(ans\) 上去的,那么更新答案要对 \(ans\) 的最值和 \(it\) 的答案
R.query(it) - st[it] + 1取一个最大值,类似于一个延迟更新。
时间复杂度 \(\Theta ((n+q)\cdot \log n)\),细节见代码。
CODE
#include<bits/stdc++.h>
#define debug() cerr<<"Line:"<<__LINE__<<endl
using namespace std;
typedef pair<int,int> pii;
const int N = 500010;
int n,a[N],res[N],q;
vector<pii> e[N];
int st[N],top;
int l[N];
template <int op = 1>
struct Bit{
int c[N];
#define lowbit(a) (a&(-a))
void init(){
memset(c,0,sizeof c);
}
void add(int x,int v){
for(int i = x;0 < i && i <= n;i += op * lowbit(i))c[i] = max(c[i],v);
}
int query(int x){
int res = 0;
for(int i = x;0 < i && i <= n;i -= op * lowbit(i))res = max(c[i],res);
return res;
}
};
Bit<1> R;
Bit<-1> ans;
void solve(){
R.init(),ans.init();
top = 0;
for(int i = 1;i<=n;++i){
while(top && a[st[top]] <= a[i])--top;
l[i] = st[top]+1;
st[++top] = i;
}
top = 0;
for(int i = 1;i<=n;++i){
while(top && a[st[top]] > a[i]){
int res = R.query(top) - st[top] + 1;
ans.add(st[top],res);
--top;
}
st[++top] = i;
int it = lower_bound(st+1,st+top+1,l[i])-st;
R.add(it,i), ans.add(st[it],i-st[it]+1);
for(pii p : e[i]){
int it = lower_bound(st+1,st+top+1,p.first)-st;
int fi = ans.query(p.first), se = R.query(it) - st[it] + 1;
res[p.second] = max(res[p.second],max(fi,se));
}
}
}
//#define printf printf(">>>> "),printf
int main(){
scanf("%d",&n); for(int i = 1;i<=n;++i)scanf("%d",&a[i]);
scanf("%d",&q); for(int i = 1,l,r;i<=q;++i){
scanf("%d%d",&l,&r);
e[r].push_back({l,i});
}
solve();
for(int i = 1;i<=n;++i)a[i] = -a[i];
solve();
for(int i = 1;i<=q;++i)printf("%d\n",res[i]);
return 0;
}
反思 & 总结
trick:
- 离线按右端点排序是处理区间问题;
- 对于两个性质相反的东西进行统计可以把所有数取反再跑一遍;
- 用树状数组维护栈。

浙公网安备 33010602011771号