10086

my first title

my first paragraph

P8037 [COCI2015-2016#7] Prokletnik

提供一种树状数组的解法

题目链接

Better experience

题意简述

一个好区间定义为:这个区间的两个端点值为整个区间的最大值和最小值。
有一段长为 n(1n500000)的序列,现在提出 Q 个询问 (Q500000) 每次有 L,R(1LRN)表示询问的区间。
求出 [L,R] 中最长的子区间,是好区间。

题目分析

套路地,我们可以离线,按照右端点排序。那么类似增量法,每次加入一个 r,维护答案并查询。

一开始的想法:单调栈预处理出向左 / 向右第一个大于 / 小于的位置,然后二维偏序,对于当前的 r 每个 i 维护的是 [i,r] 这个区间的答案。但不知道怎么继续,难点在于每一个偏序找到的点都要大范围地更新答案。

首先令右端点的值为最大值,相反的将每个 ai 取反再跑一遍即可。我们维护一个单调递减的栈,表示这些可以被 i 以及后续更新到的位置。和其他题解中的方式基本相同,但我们先思考线段树到底是怎么维护的。是不是对于栈中的点的存在与否进行 pos 标记(即修改),再以打 tag 标记的方式去将答案放到线段树的节点上。既然是以栈中元素作为中心,为什么不直接开一个树状数组去维护栈中元素?

具体实现如下:

  1. 维护一个树状数组 R,表示对应的栈中的元素作为左端点时的最大右端点,维护前缀最大值。
  2. 维护一个树状数组 ans,表示 i 节点为左端点的最大答案,维护后缀最大值。
  3. 每一次要加入当前的 ai,将栈中不再会被更新的元素弹掉,并将其答案 R.query(top)-st[top]+1 加入到 ans 中。
  4. 然后将 i 入栈,二分找到栈中的第一个合法位置,并修改 Rans。由于 R 维护的是前缀最值,修改是改其后面的部分,故栈中所有要被更新的左端点都被更新了。
  5. 处理询问时,令栈中第一个大于等于当前询问的位置为 it。由于我们先前更新时对于 R 的更新时没有全部都更新到 ans 上去的,那么更新答案要对 ans 的最值和 it 的答案 R.query(it) - st[it] + 1 取一个最大值,类似于一个延迟更新。

时间复杂度 Θ((n+q)logn),细节见代码。

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

  1. 离线按右端点排序是处理区间问题;
  2. 对于两个性质相反的东西进行统计可以把所有数取反再跑一遍;
  3. 用树状数组维护栈。
posted @   Luzexxi  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示