传送门

感觉还是挺有思维难度的一道题。蒟蒻想了不短时间才从暴力$O(n^3)$优化到$O(nlogn)$

纯暴力

$O(n^3)$ 暴力枚举区间内的点对,暴力扫描区间最大值。

稍微好点的暴力

最大值可以用线段树维护,优化到 $O(n^2logn)$。

(其实$st$表能达到 $O(n^2)$ ,不过蒟蒻早忘了咋写了。。。总之最大值可以用数据结构优化)

(重点!)高端的优化

似乎到上面就不是很好想了。瓶颈就在于枚举点对的 $n^2$。可不可以少枚举点东西?

一个点对 $(i,j)$ 的贡献与三个值有关:$a[i],a[j],max(a[k])(i<k<j)$

不枚举$i,j$,那就枚举中间的 $a[k]$。这里引入两个值:定义 $ll[i]$ 为从点 $i$ 向左走,直到遇到第一个大于它的数的位置(没有则为0);同理,$rr[i]$就是向右走(没有则为n+1)。

也就是说,点 $i$ 是区间$[ll[i]+1,rr[i]-1]$的最大值,且满足这个区间长度最小

这样,枚举这个点(记为 $i$ )的时候:(区间记为$[L,R]$)

  • 如果 $ll[i]$ 与 $rr[i]$ 都在区间中,就会有一点对 $(ll[i],rr[i])$满足条件 $1$。
  • 如果 $ll[i]$ 在区间中,那么每个点对 $(ll[i],k)(i<k<min(rr[i],R))$ 满足条件 $2$。(因为 $a[k]<a[i]$ ,$a[i]<a[ll[i]]$ , $a[i]$ 为区间 $[ll[i]+1,k-1]$的最大值。注意不能走出去所以取min)
  • 如果 $rr[i]$ 在区间中,那么每个点对 $(k,rr[i])(max(ll[i],L)<k<i)$ 满足条件 $2$。(原因同上)

这样就能得到 $O(n^2)$ 的做法。

顶级的优化

前面铺垫这么多,终于轮到正解出场了!

根据上面,我们需要一种方法,能快速得知:

  • 每个 $ll[i]$ 在区间内的数的 $min(rr[i],R)-i$
  • 每个 $rr[i]$ 在区间内的数的 $i-max(ll[i],L)$
  • $ll[i]$,$rr[i]$都在区间内数的个数。

虽然有点麻烦,不过本题只有询问,是不是可以离线下来搞一搞呢?

先处理第一种。从右往左扫描数列,遇到某个数的 $ll[i]$ 再把这个数产生的贡献用某种数据结构维护起来,遇到某个询问的左端点就直接查询,这样保证了只有 $ll[i]$ 在区间内的数才会产生贡献

具体来说:用线段树维护。遇到 $ll[i]$ 就把 $[i+1,rr[i]-1]$(注意不能取两端)加 $1$,遇到询问左端点就查询 $[L,R]$ 的和。仔细想想就会发现,这样也会在查询时相当于取了$min(rr[i],R)$。

第二种倒过来扫就好了。

第三种正着扫倒着扫皆可。以倒着扫为例:遇到 $ll[i]$ 把$[rr[i],n]$加 $1$(意为询问右端点落在$[rr[i],n]$都会覆盖到$rr[i]$,就有 1 贡献),这样查询时单点查 $R$ 即可。

时间复杂度:$O(nlogn)$(常数有点大最慢$1100ms+$。。。)

细节:1.关于预处理$ll$、$rr$:蒟蒻好像想复杂了,没有什么好方法,只能记录下位置,按权值排序后造棵平衡树倒着扫数列,插入每个点的位置,$ll$、$rr$分别查前驱后继。。。

2.统计的答案要开long long!

$update:$

3.有一点忘了说了:对于情况1,满足$i+1=j​$同样会有贡献,而该方法统计不上,所以输出时直接加上就好。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>

#define maxn 200005
#define inf 0x3f3f3f

using namespace std;

inline int read(){
    int x=0,y=0;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')y=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return y?-x:x;
}
template<typename T>
inline T read(){
    T x=0;
    int y=0;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')y=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return y?-x:x;
}//快读
inline int ran(){
    static int seed=45562;
    return seed=seed*48271LL%2147483647;
}//一个优化常数的随机数函数
int a[maxn],pos[maxn],ll[maxn],rr[maxn],n;
vector<int>lq[maxn],rq[maxn],li[maxn],ri[maxn];
//分别用来存询问左端点、右端点和ll、rr的位置情况
//vector可以用链式前向星代替,常数小。不过我懒了。。。
struct Question{
    int l,r;
    long long ans;
}q[maxn];
struct Treap{
#define ls(x) ls[x]
#define rs(x) rs[x]
    int dat[maxn],ls[maxn],rs[maxn],ra[maxn],root,cnt;
    void right(int &node){
        int rec=ls(node);
        ls(node)=rs(rec);
        rs(rec)=node;
        node=rec;
    }
    void left(int &node){
        int rec=rs(node);
        rs(node)=ls(rec);
        ls(rec)=node;
        node=rec;
    }
    void insert(int &node,int d){
        if(!node){
            node=++cnt;
            dat[node]=d;
            ra[node]=ran();
            return;
        }
        if(dat[node]<d){
            insert(rs(node),d);
            if(ra[rs(node)]>ra[node])left(node);
        }
        else {
            insert(ls(node),d);
            if(ra[ls(node)]>ra[node])right(node);
        }
    }
    int getpre(int d){
        int ans=0,node=root;
        while(node){
            if(dat[node]<d)ans=max(ans,dat[node]),node=rs(node);
            else node=ls(node);
        }
        return ans;
    }
    int getnex(int d){
        int ans=n+1,node=root;
        while(node){
            if(dat[node]<d)node=rs(node);
            else ans=min(ans,dat[node]),node=ls(node);
        }
        return ans;
    }    
}tr;//平衡树
#undef ls
#undef rs
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
struct Segment_Tree{
    int dat[maxn<<2],tag[maxn<<2];
    inline void update(int node){
        dat[node]=dat[ls(node)]+dat[rs(node)];
    }
    inline void datadown(int l,int r,int node,int d){
        dat[node]+=(r-l+1)*d;
        tag[node]+=d;
    }
    inline void pushdown(int l,int r,int node){
        int mid=l+r>>1;
        datadown(l,mid,ls(node),tag[node]);
        datadown(mid+1,r,rs(node),tag[node]);
        tag[node]=0;
    }
    void add(int L,int R,int l,int r,int node){
        if(L<=l&&R>=r){
            dat[node]+=r-l+1;
            ++tag[node];
            return;
        }
        if(tag[node])pushdown(l,r,node);
        int mid=l+r>>1;
        if(L<=mid)add(L,R,l,mid,ls(node));
        if(R>mid)add(L,R,mid+1,r,rs(node));
        update(node);
    }
    long long ask(int L,int R,int l,int r,int node){
        if(L<=l&&R>=r)return (long long)dat[node];
        if(tag[node])pushdown(l,r,node);
        int mid=l+r>>1;
        long long ans=0;
        if(L<=mid)ans=ask(L,R,l,mid,ls(node));
        if(R>mid)ans+=ask(L,R,mid+1,r,rs(node));
        return ans;
    }
}st1,st2,st3;//空间够大开三棵线段树QwQ
int main(){
    n=read();
    int m=read();
    long long p1=read<long long>(),p2=read<long long>();
    for(register int i=1;i<=n;++i){
        a[i]=read();
        pos[a[i]]=i;
    }
    sort(a+1,a+1+n);
    for(register int i=n;i;--i){
        int L=tr.getpre(pos[a[i]]),R=tr.getnex(pos[a[i]]);
        ll[pos[a[i]]]=L;
        rr[pos[a[i]]]=R;
        if(L!=-inf)li[L].push_back(pos[a[i]]);
        if(R!=inf)ri[R].push_back(pos[a[i]]);
        tr.insert(tr.root,pos[a[i]]);    
    }
    for(register int i=1;i<=m;++i)
        q[i].l=read(),q[i].r=read(),lq[q[i].l].push_back(i),rq[q[i].r].push_back(i);
    for(register int i=n;i;--i){
        for(register int j=0;j<li[i].size();++j){
            if(li[i][j]+1<rr[li[i][j]])st1.add(li[i][j]+1,rr[li[i][j]]-1,0,n+1,1);
            st3.add(rr[li[i][j]],n+1,0,n+1,1);
        }
        for(register int j=0;j<lq[i].size();++j)
            q[lq[i][j]].ans+=st1.ask(i,q[lq[i][j]].r,0,n+1,1)*p2+st3.ask(q[lq[i][j]].r,q[lq[i][j]].r,0,n+1,1)*p1;
    }
    for(register int i=1;i<=n;++i){
        for(register int j=0;j<ri[i].size();++j)
            if(ll[ri[i][j]]+1<ri[i][j])st2.add(ll[ri[i][j]]+1,ri[i][j]-1,0,n+1,1);
        for(register int j=0;j<rq[i].size();++j)
            q[rq[i][j]].ans+=st2.ask(q[rq[i][j]].l,i,0,n+1,1)*p2;
    }
    for(register int i=1;i<=m;++i)
        printf("%d\n",q[i].ans+(long long)(q[i].r-q[i].l)*p1);
}