题解 luoguP3722 【[AHOI2017&HNOI2017]影魔】

传送门

首先整个序列是一个排列,这是一个很重要的性质

考虑 ( i , j ) (i,j) (i,j)什么时候会产生贡献:

  1. k i , k j k_i,k_j ki,kj为区间 ( i , j ) (i,j) (i,j)的最大值和次大值,贡献为 p 1 p1 p1
  2. k i , k j k_i,k_j ki,kj其中一个为区间最大值,另一个不是次大值,贡献为 p 2 p2 p2

维护出每个 i i i位置左边右边第一个比它大的位置,记作 L i , R i L_i,R_i Li,Ri,这个可以用单调栈求。于是 ( L i , R i ) (L_i,R_i) (Li,Ri) ( i , i + 1 ) (i,i+1) (i,i+1)的贡献为 p 1 p1 p1 ( L i + 1... i − 1 , R i ) (L_i+1...i-1,R_i) (Li+1...i1,Ri) ( L i , i + 1... R i − 1 ) (L_i,i+1...R_i-1) (Li,i+1...Ri1)的贡献为 p 2 p2 p2

可以发现如果有 ( p , q ) (p,q) (p,q)满足 p < L i , q < R i pp<Li,q<Ri ( p , q ) (p,q) (p,q)如果有贡献,则会算在另一个 i i i上,所以不会算重

对于 ( i , i + 1 ) (i,i+1) (i,i+1),直接根据询问区间长度算贡献

对于 ( L i , R i ) (L_i,R_i) (Li,Ri),我们在枚举到 R i R_i Ri时在 L i L_i Li上加上贡献

对于 ( L i + 1... i − 1 , R i ) (L_i+1...i-1,R_i) (Li+1...i1,Ri),我们在枚举到 R i R_i Ri时在 L i + 1... i − 1 L_i+1...i-1 Li+1...i1这段区间加上贡献

对于 ( L i , i + 1... R i ) (L_i,i+1...R_i) (Li,i+1...Ri),同上一条

回答一个询问 [ a , b ] [a,b] [a,b]时,如果在枚举到 b b b时直接求 [ a , b ] [a,b] [a,b]的区间和,会存在一些 ( i , j ) (i,j) (i,j)满足 i < a , a < j < b ii<a,a<j<b的不合法的贡献。所以我们需要减去枚举到 a − 1 a-1 a1 [ a , b ] [a,b] [a,b]的区间和,这样有贡献的 ( i , j ) (i,j) (i,j)就都在区间内

C o d e B e l o w Code Below CodeBelow

#include
#define ts cout<<"ok"<
#define int long long
#define hh puts("")
#define pc putchar
#define ls(x) ((x)<<1)
#define rs(x) ((x)<<1|1)
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
//char buf[1<<21],*p1=buf,*p2=buf;
using namespace std;
const int N=200005;
int n,m,p1,p2,a[N],st[N],top,L[N],R[N],cnt,tot;
int tr[N<<2],tag[N<<2],ans[N];
struct que{
    int x,l,r,v,id;
    friend bool operator < (que A,que B){
        return A.x<B.x;
    }
}q[N<<1];
struct ope{
    int x,l,r,v;
    friend bool operator < (ope A,ope B){
        return A.x<B.x;
    }
}op[N<<2];
inline int read(){
    int ret=0,ff=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)){ret=ret*10+(ch^48);ch=getchar();}
    return ret*ff;
}
void write(int x){if(x<0){x=-x,pc('-');}if(x>9) write(x/10);pc(x%10+48);}
void writeln(int x){write(x),hh;}
void writesp(int x){write(x),pc(' ');}
void push_down(int l,int r,int k){
    int mid=(l+r)>>1;
    tag[ls(k)]+=tag[k];
    tag[rs(k)]+=tag[k];
    tr[ls(k)]+=tag[k]*(mid-l+1);
    tr[rs(k)]+=tag[k]*(r-mid);
    tag[k]=0;
}
void update(int l,int r,int x,int y,int v,int k){
    if(x<=l&&r<=y){
        tr[k]+=(r-l+1)*v;
        tag[k]+=v;
        return;
    }
    push_down(l,r,k);
    int mid=(l+r)>>1;
    if(x<=mid) update(l,mid,x,y,v,ls(k));
    if(mid+1<=y) update(mid+1,r,x,y,v,rs(k));
    tr[k]=tr[ls(k)]+tr[rs(k)];
}
int query(int l,int r,int x,int y,int k){
    if(x<=l&&r<=y) return tr[k];
    push_down(l,r,k);
    int mid=(l+r)>>1,res=0;
    if(x<=mid) res+=query(l,mid,x,y,ls(k));
    if(mid+1<=y) res+=query(mid+1,r,x,y,rs(k));
    return res;
}
signed main(){
    n=read(),m=read(),p1=read(),p2=read();
    for(int i=1;i<=n;i++) a[i]=read();
    st[top=0]=0;
    for(int i=1;i<=n;i++){
        while(top&&a[i]>a[st[top]]) top--;
        L[i]=st[top];st[++top]=i;
    }
    st[top=0]=n+1;
    for(int i=n;i>=1;i--){
        while(top&&a[i]>a[st[top]]) top--;
        R[i]=st[top];st[++top]=i;
    }
    for(int i=1;i<=m;i++){
        int l=read(),r=read();ans[i]=(r-l)*p1;//(i,i+1)的贡献 
        q[++cnt]=(que){l-1,l,r,-1,i};
        q[++cnt]=(que){r,l,r,1,i};
    }
    sort(q+1,q+cnt+1);
    for(int i=1;i<=n;i++){
        if(L[i]>=1&&R[i]<=n) op[++tot]=(ope){R[i],L[i],L[i],p1};
        if(L[i]>=1&&R[i]-1>=i+1) op[++tot]=(ope){L[i],i+1,R[i]-1,p2};
        if(L[i]+1<=i-1&&R[i]<=n) op[++tot]=(ope){R[i],L[i]+1,i-1,p2};
    }
    sort(op+1,op+tot+1);
    for(int i=1,j=1;i<=cnt;i++){
        while(j<=tot&&op[j].x<=q[i].x){
            update(1,n,op[j].l,op[j].r,op[j].v,1);
            j++;
        }
        ans[q[i].id]+=q[i].v*query(1,n,q[i].l,q[i].r,1);
    }
    for(int i=1;i<=m;i++) writeln(ans[i]);
    return 0;
}

你可能感兴趣的:(题解)