peiwenjun's blog 没有知识的荒原

P8037 [COCI2015-2016#7] Prokletnik 题解

题目描述

定义一个序列是魔法序列,当且仅当中间所有元素均在第一个和最后一个元素之间。

给定一个长为 \(n\) 的序列 \(a\)\(q\) 次询问 \(a[l\sim r]\) 中魔法序列长度最大值。

数据范围

  • \(1\le n,q\le 5\cdot 10^5,1\le a_i\le 10^9\)
  • \(1\le l\le r\le n\)

时间限制 \(\texttt{4s}\) ,空间限制 \(\texttt{128MB}\)

分析

不妨只考虑左端点为最小值,右端点为最大值的魔法序列。(令 \(a_i\gets -a_i\) 再做一遍即可)

\(x_i\) 表示左端点为 \(i\) 时,右端点的最大值。线段树维护 \(x_i-i+1\) 的最大值。离线对 \(r\) 扫描线,查询 \([l,r]\) 区间 \(\max\) 即可。

\(a[l\sim r]\) 是魔法序列当且仅当 \(R_l\lt r,L_r\lt l\) 。其中 \(L_i\) 为左边第一个比 \(h_i\) 大的位置, \(R_i\) 为右边第一个比 \(h_i\) 小的位置,可以用单调栈预处理。

\(R_l\lt r\) 的限制相对容易刻画,可以看成这个点仅在区间 \([l,R_l-1]\) 中出现,扫到 \(R_l\) 时将其贡献删除即可。

\(L_r\lt l\) 的限制相当于给一个后缀赋值 \(x_i=r\)

由于线段树维护的不是 \(x_i\) 而是 \(x_i-i+1\) ,所以很可惜不能直接区间赋值。

但我们可以执行区间加,单调栈维护 \(x_i\) 相同的段,弹栈同时在线段树上减掉原来的贡献,最后加入一个完整的段。

每个段只会被删掉一次,均摊只有 \(\mathcal O(n)\) 次线段树上的操作。

注意已经删除的点仍然可以产生贡献(只是贡献不再更新),因此需要另开一个线段树存储已经删除的点的贡献。

时间复杂度 \(\mathcal O(n\log n)\)

#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=5e5+5,inf=1e9;
int l,n,q,r;
int a[maxn],x[maxn],st[maxn],res[maxn];
int L[maxn],R[maxn];
vector<int> del[maxn];
vector<pii> vec[maxn];
struct sgmt
{
    int op;
    struct node
    {
        int l,r,mx,add;
    }f[maxn<<2];
    void pushup(int p)
    {
        f[p].mx=max(f[ls].mx,f[rs].mx);
    }
    void pushadd(int p,int v)
    {
        f[p].mx+=v,f[p].add+=v;
    }
    void pushdown(int p)
    {
        if(!f[p].add) return ;
        pushadd(ls,f[p].add),pushadd(rs,f[p].add),f[p].add=0;
    }
    void build(int p,int l,int r)
    {
        f[p].l=l,f[p].r=r,f[p].add=0;
        if(l==r) return f[p].mx=op*(-l+1),void();
        int mid=(l+r)/2;
        build(ls,l,mid);
        build(rs,mid+1,r);
        pushup(p);
    }
    void modify(int p,int l,int r,int v)
    {
        if(l<=f[p].l&&f[p].r<=r) return pushadd(p,v);
        if(l>f[p].r||r<f[p].l) return ;
        pushdown(p);
        modify(ls,l,r,v);
        modify(rs,l,r,v);
        pushup(p);
    }
    int query(int p,int l,int r)
    {
        if(l<=f[p].l&&f[p].r<=r) return f[p].mx;
        if(l>f[p].r||r<f[p].l) return -inf;
        pushdown(p);
        return max(query(ls,l,r),query(rs,l,r));
    }
}t1,t2;
void chmax(int &x,int y)
{
    if(x<=y) x=y;
}
void solve()
{
    for(int i=1;i<=n;i++) del[i].clear();
    t1.build(1,1,n),t2.build(1,1,n);
    for(int i=1,top=0;i<=n;i++)
    {
        while(top&&a[st[top]]<=a[i]) top--;
        L[i]=st[top]+1,st[++top]=i;
    }
    for(int i=n,top=0;i>=1;i--)
    {
        while(top&&a[st[top]]>=a[i]) top--;
        R[i]=st[top],st[++top]=i;
        if(top) del[R[i]].push_back(i);
    }
    for(int i=1,top=0;i<=n;i++)
    {
        for(auto p:del[i]) t2.modify(1,p,p,t1.query(1,p,p)),t1.modify(1,p,p,-inf);
        int cur=i;
        while(top&&st[top]>=L[i]) t1.modify(1,st[top],cur-1,-x[top]),cur=st[top--];
        if(top) t1.modify(1,L[i],cur-1,-x[top]);
        st[++top]=L[i],x[top]=i,t1.modify(1,L[i],i,i);
        for(auto p:vec[i]) chmax(res[p.se],max(t1.query(1,p.fi,i),t2.query(1,p.fi,i)));
    }
}
int main()
{
    scanf("%d",&n),t1.op=1;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    scanf("%d",&q);
    for(int i=1;i<=q;i++) scanf("%d%d",&l,&r),vec[r].push_back(mp(l,i));
    solve();
    for(int i=1;i<=n;i++) a[i]=inf-a[i];
    solve();
    for(int i=1;i<=q;i++) printf("%d\n",res[i]);
    return 0;
}

posted on 2023-02-08 18:39  peiwenjun  阅读(21)  评论(0)    收藏  举报

导航