COCI2015/2016 #7 F PROKLETNIK
COCI2015/2016 #7 F PROKLETNIK
题目链接:Croatian Open Competition in Informatics (hsin.hr)
首先区间的左右端点是最小值还是最大值并没有固定,所以我们得对于两种情况做两遍。但是我们可以先求出左端点是最小值右端点是最大值的最长子区间,然后将 \(a\) 数组中的每个数组取相反数,再做一遍,求出左端点是最小值右端点是最大值的最长子区间,然后取极值即可。(\(a\) 数组指题目给出的原数组。)
那么我们将区间两端的是最小值还是最大值固定下来之后,就比较容易去思考了。
我们发现,好像没有一个数据结构容易在线维护这个问题,那么我们考虑离线处理,并且将区间按右端点排序。离线排序过后,从左往右遍历 \(a\) 数组,很容易想到,如果我们能够维护一个数组 \(b\),\(b_i\) 一个最长的符合题意的区间且它的左端点在 \(i\),而右端点在 \(i,u\) 之间。(\(u\) 指现在遍历到的位置)那么我们对于一个询问 \(l,r\)。就在遍历到 \(r\) 时查询 \(max_{i=l}^{r}{b_i}\),这就是答案。
考虑现在遍历到 \(u\) 这个位置,且设 \(v\) 为第一个小于 \(u\) 且 \(a_v>a_u\) 的位置。即,在 \(u\) 左边第一个比 \(a_u\) 大的数的下标。显然 \(u\) 能更新的位置只能是 \((v,u]\) 这个区间。但在这个区间内也不是每个位置的 \(b\) 都能更新的,对于一个位置 \(pos\) 来说,如果有一个 \(x\) 满足\(x\in [pos+1,u],a_x<a_{pos}\),那么 \(pos\) 这个位置就不能更新,所以我们还要维护能更新的一个序列。显然的,对于 \(pos\) 来说,当 \(u\) 遍历到在 \(pos\) 右边且第一个小于 \(a_{pos}\) 的位置时,\(pos\) 这个点就不能被更新了。那么我们可以维护一个单调不降的单调栈,当加入 \(u\) 时,对于每一个因加入 \(u\) 而弹出栈的位置 \(x\) (此 \(x\) 与上文提到的 \(x\) 意义不同)来说,\(u\) 就是在 \(x\) 右边,且第一个小于 \(a_u\) 的值的位置。由此可得所有在这个单调栈里面的位置就是右边没有比它小的值的位置。然后我们在这个单调栈中找到在 \(v\) 右边的位置(\(v\) 的意义与上文提到的相同)\(y\),而 \(y\) 到栈顶所存的位置就是可以更新的位置,让这些位置的 \(b\) 值更新成 \(u\) 即可。这个可以用线段树来维护(以栈中位置为下标)。那么不能更新的位置的 \(b\) 值已经固定了,我们可以再开一个线段树(以 \(a\) 中位置为下标),对于线段树中的叶子,如果它管辖 \(i\),那么它记录的就是 \(b_i\) 的值。
查询答案是,在无法再更新和这次能更新的范围内查询 \(max_{i=l}^{r}{b_i}\) 就好了,查询的结果就是答案。
归纳一下流程:
- 维护一个单调不降的单调栈 \(s1\),和一个用来求第一个在它左边的大于它的值位置的单调栈 \(s2\)。
- 弹出 \(s1\) 中大于 \(a_i\) 的值(\(i\) 为目前遍历到的位置),并且将弹出的位置加入到无法再更新的线段树中。将 \(i\) 加入 \(s1\)。
- 利用 \(s2\) 求出 \(l\),满足 \(l\) 是第一个小于 \(i\) 且 \(a_l>a_i\) 的位置。在 \(s1\) 中找到第一个位置在 \(l\) 右边的位置 \(x\)。更新 \(s1\) 中 \(x\) 到栈顶中所存位置的 \(b\) 值。
- 对于每右端点在 \(i\) 的询问,在刚刚更新过的 \(b\) 值与不可再更新的线段树中得到的 \(max_{i=l}^{r}{b_i}\) 取一个最大值,即为答案。
- 将 \(a_i\) 取相反数,再重复一遍上述流程更新答案。
事实上这题还可以用树状数组代替线段树,不过这种写法虽然方便却并不好理解。我理解了一个下午。这里就贺一下学长的线段树代码再贴出我自己写的树状数组代码。
线段树写法:
#include<bits/stdc++.h>
using namespace std;
#define M 500005
#define ls p<<1
#define rs p<<1|1
int n,A[M];
int q,ans[M];
int MonInc[M],MI_top;//单调递增栈
int MonDec[M],MD_top;//单调递减栈
struct Q{int id,x;};
vector <Q> Qs[M];
int res[M<<2],lazy[M<<2],Apt[M<<2];
void build(int l,int r,int p){
res[p]=lazy[p]=Apt[p]=0;
if(l==r)return;
int mid=l+r>>1;
build(l,mid,ls);
build(mid+1,r,rs);
}
void down(int p){
if(!lazy[p])return;
lazy[ls]=max(lazy[p],lazy[ls]);
if(Apt[ls])res[ls]=max(res[ls],lazy[p]-Apt[ls]+1);
lazy[rs]=max(lazy[p],lazy[rs]);
if(Apt[rs])res[rs]=max(res[rs],lazy[p]-Apt[rs]+1);
lazy[p]=0;
}
void up(int p){
if(Apt[ls])Apt[p]=Apt[ls];
else Apt[p]=Apt[rs];
res[p]=max(res[ls],res[rs]);
}
void Update_point(int l,int r,int k,int x,int p){
if(l==r){
Apt[p]=x;
return;
}
down(p);
int mid=l+r>>1;
if(k<=mid)Update_point(l,mid,k,x,ls);
else Update_point(mid+1,r,k,x,rs);
up(p);
}
void Update(int l,int r,int L,int R,int x,int p){
if(L==l&&R==r){
lazy[p]=max(lazy[p],x);
if(Apt[p])res[p]=max(res[p],x-Apt[p]+1);
return;
}
down(p);
int mid=l+r>>1;
if(R<=mid)Update(l,mid,L,R,x,ls);
else if(L>mid)Update(mid+1,r,L,R,x,rs);
else Update(l,mid,L,mid,x,ls),Update(mid+1,r,mid+1,R,x,rs);
up(p);
}
int Query(int l,int r,int L,int R,int p){
if(l==L&&r==R)return res[p];
down(p);
int mid=l+r>>1;
if(R<=mid)return Query(l,mid,L,R,ls);
else if(L>mid)return Query(mid+1,r,L,R,rs);
else return max(Query(l,mid,L,mid,ls),Query(mid+1,r,mid+1,R,rs));
}
void Addpoint(int x){
while(MD_top&&A[x]>=A[MonDec[MD_top]])--MD_top;
int l=MonDec[MD_top]+1;//A[x]对l到x的区间有贡献
while(MI_top&&A[x]<A[MonInc[MI_top]]){
Update_point(1,n,MonInc[MI_top],0,1);
--MI_top;
}
MonDec[++MD_top]=x;
MonInc[++MI_top]=x;
Update_point(1,n,x,x,1);
Update(1,n,l,x,x,1);
}
void solve(){
MI_top=MD_top=0;
build(1,n,1);
for(int i=1;i<=n;++i){
Addpoint(i);
int len=Qs[i].size();
for(int j=0;j<len;++j){
int Id=Qs[i][j].id,l=Qs[i][j].x;
int tmp=Query(1,n,l,i,1);
ans[Id]=max(ans[Id],tmp);
}
}
}
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);
Qs[r].push_back(Q{i,l});
}
solve();
for(int i=1;i<=n;++i)A[i]=-A[i];
solve();
for(int i=1;i<=q;++i)
printf("%d\n",ans[i]);
return 0;
}
树状数组写法
#include<bits/stdc++.h>
#define lowbit(i) (i&(-i))
using namespace std;
const int MAXN = 5e5+5;
int n,m,t1[MAXN],t2[MAXN],s1[MAXN],s2[MAXN],a[MAXN],ans[MAXN];
struct Ques
{
int l,id;
};
vector <Ques> vec[MAXN];
void upd(int *t,int pos,int x)
{
for(int i=pos;i<=n;i+=lowbit(i))
t[i]=max(t[i],x);
}
int query(int *t,int pos)
{
int ans=0;
for(int i=pos;i;i-=lowbit(i))
ans=max(ans,t[i]);
return ans;
}
void solve()
{
int r1,r2;r1=r2=0;
memset(t1,0,sizeof t1);memset(t2,0,sizeof t2);
for(int i=1;i<=n;++i)
{
while(r1&&a[s1[r1]]>a[i]){upd(t1,n-s1[r1]+1,query(t2,r1)-s1[r1]+1);--r1;}
s1[++r1]=i;
while(r2&&a[s2[r2]]<=a[i]) --r2;
int cur=upper_bound(s1+1,s1+1+r1,s2[r2])-s1;
upd(t2,cur,i);upd(t1,n-s1[cur]+1,i-s1[cur]+1);
s2[++r2]=i;
for(int j=0;j<vec[i].size();++j)
{
int id=vec[i][j].id,l=vec[i][j].l;
int now=lower_bound(s1,s1+1+r1,l)-s1;
ans[id]=max(ans[id],max(query(t1,n-l+1),query(t2,now)-s1[now]+1));
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
scanf("%d",&m);
for(int i=1;i<=m;++i)
{
int l,r;
scanf("%d %d",&l,&r);
vec[r].push_back(Ques{l,i});
}
solve();
for(int i=1;i<=n;++i)
a[i]=-a[i];
solve();
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]);
return 0;
}
值得一提的是,在官方题解中给出的标程使用了竞赛树来维护这个信息,但是由于我太菜了,不会竞赛树,这里就只贴出代码。
竞赛树写法:
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <iostream>
#include <stack>
#define TRACE(x) cerr << #x << " = " << x << endl
#define _ << " _ " <<
#define FOR(i, a, b) for (int i = (a); i < (b); ++i)
#define REP(i, n) FOR (i, 0, n)
using namespace std;
typedef long long llint;
typedef pair<int,int> pii;
const int MAXN = 1001000;
const int offset = 1 << 20;
struct tournament {
int alive[2 * offset];
int prop_max[2 * offset];
int real_max[2 * offset];
void clear() {
fill(alive, alive + 2 * offset, -1);
fill(prop_max, prop_max + 2 * offset, 0);
fill(real_max, real_max + 2 * offset, 0);
}
void set_alive(int x, int lo, int hi, int k, int val) {
if (k < lo || k >= hi) return;
if (lo + 1 == hi) {
alive[x] = val;
return;
}
propagate(x);
int mid = (lo + hi) / 2;
set_alive(2 * x, lo, mid, k, val);
set_alive(2 * x + 1, mid, hi, k, val);
update(x);
}
void set_max(int x, int lo, int hi, int from, int to, int val) {
if (lo >= to || hi <= from) return;
if (lo >= from && hi <= to) {
prop_max[x] = val;
real_max[x] = max(real_max[x], alive[x] == -1 ? 0 : val - alive[x]);
return;
}
propagate(x);
int mid = (lo + hi) / 2;
set_max(2 * x, lo, mid, from, to, val);
set_max(2 * x + 1, mid, hi, from, to, val);
update(x);
}
int get_max(int x, int lo, int hi, int from, int to) {
if (lo >= to || hi <= from) return 0;
if (lo >= from && hi <= to) return real_max[x];
propagate(x);
int mid = (lo + hi) / 2;
return max(get_max(2 * x, lo, mid, from, to),
get_max(2 * x + 1, mid, hi, from, to));
}
void propagate(int x) {
prop_max[2 * x] = max(prop_max[2 * x], prop_max[x]);
prop_max[2 * x + 1] = max(prop_max[2 * x + 1], prop_max[x]);
real_max[2 * x] = max(real_max[2 * x],
alive[2 * x] == -1 ? 0 : prop_max[2 * x] - alive[2 * x]);
real_max[2 * x + 1] = max(real_max[2 * x + 1],
alive[2 * x + 1] == -1 ? 0 : prop_max[2 * x + 1] - alive[2 * x + 1]);
prop_max[x] = 0;
}
void update(int x) {
alive[x] = alive[2 * x] == -1 ? alive[2 * x + 1] : alive[2 * x];
real_max[x] = max(real_max[2 * x], real_max[2 * x + 1]);
}
};
int n, q;
int a[MAXN];
pii b[MAXN];
vector<int> queries[MAXN];
int sol[MAXN];
stack<int> maxs;
stack<int> mins;
tournament tour;
void clear() {
while (maxs.size()) {
maxs.pop();
}
while (mins.size()) {
mins.pop();
}
tour.clear();
}
void insert(int x) {
while (maxs.size() && a[x] >= a[maxs.top()]) {
maxs.pop();
}
int from = maxs.size() ? maxs.top() + 1 : 0;
while (mins.size() && a[x] < a[mins.top()]) {
tour.set_alive(1, 0, offset, mins.top(), -1);
mins.pop();
}
maxs.push(x);
mins.push(x);
tour.set_alive(1, 0, offset, x, x);
tour.set_max(1, 0, offset, from, x + 1, x + 1);
}
void solve() {
clear();
for (int i = 0; i < n; ++i) {
insert(i);
for (int j : queries[i]) {
sol[j] = max(sol[j], tour.get_max(
1, 0, offset, b[j].first, b[j].second + 1));
}
}
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%d", a + i);
}
scanf("%d", &q);
for (int i = 0; i < q; ++i) {
scanf("%d%d", &b[i].first, &b[i].second);
--b[i].first;
--b[i].second;
queries[b[i].second].push_back(i);
}
solve();
for (int i = 0; i < n; ++i) {
a[i] = -a[i];
}
solve();
for (int i = 0; i < q; ++i) {
printf("%d\n", sol[i]);
}
return 0;
}
总结:神仙题,不做评价。