[HNOI2017]影魔sf

题目大意

给出 1 n 的一个排列 {kn}
定义第一类点对 (i,j) 满足 i<j ,且 max{ks|i<s<j}<min(ki,kj)
定义第二类点对 (i,j) 满足 i<j ,且 min(ki,kj)<max{ks|i<s<j}<max(ki,kj) .
之后给出 m 个询问,每次询问一个区间 [a,b] 中, s1p1+s2p2 的值(记第一类点对的数量为 s1 ,第二类点对的数量为 s2 )。

数据范围

1n,m200000,1p1,p21000 .

解题报告

定义第三类点对 (i,j) 满足 i<j ,且 max{ks|i<s<j}<max(ki,kj) ,记第三类点对的数量为 s3 ,则有 s2=s3s1 .

考虑 s1 的计算方法:
如果 ki<kj ,那么 j 只能是 i 右边第一个比 ki 更大的位置;如果 kj<ki ,同理 i 只能是 j 左边第一个比 kj 更大的位置。
previ i 左边第一个比 ki 更大的位置,若不存在则为 0 ;记 nexti i 右边第一个比 ki 更大的位置,若不存在则为 n+1 . 则对于询问 [a,b] s1=i=ab[aprevib]+i=ab[anextib] .

考虑 s3 的计算方法:
如果 ki<kj ,则区间 [i,j] j 处取得最大值;如果 kj<ki , 则区间 [i,j] i 处取得最大值。
则如果固定 i ,合法的 j 满足 i<j<nexti ;同理如果固定 j ,合法的 i 满足 prevj<i<j .

有了前面的结论之后,我们可以使用一种离线算法从左到右、从右到左分别计算 ki<kj kj<ki 的情况。

对于 ki<kj 的情况,将询问 [a,b] 拆为 Q(b,a,b)Q(a1,a,b) Q(i,l,r) 表示右端点小于等于 i ,左端点属于 [l,r] 的情况。

不断移动右端点,当右端点移动到 i 时,用单调栈求出 previ ,在树状数组 bit1 中将 previ 对应的位置都加 1 ,之后在 bit1 中询问 [a,b] 的区间和,就得到了右端点小于等于 i ,左端点属于 [a,b] 的第一类点对的数量。再用两个树状数组 bit2 bit3 维护区间修改和区间求和,当右端点移动到 i 时,对区间 [previ+1,i1] 都加上 1 ,然后再在树状数组中询问 [a,b] 的区间和就得到了右端点小于等于 i ,左端点属于 [a,b] 的第三类点对的数量。

对于 kj<ki 的情况,与 ki<kj 类似,不再赘述。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

int gint(){
    char c; int f=1;
    while(c=getchar(),c<48||c>57)
        if(c=='-')f=-1;
    int x=0;
    for(;c>47&&c<58;c=getchar()){
        x=x*10+c-48;
    }
    return x*f;
}

#define max_N 200005
typedef long long ll;

int n,m,p1,p2,l[max_N],r[max_N],k[max_N];

ll cnt1[max_N],cnt2[max_N];

struct QY{
    int l,r,id,f,next;
    QY(int l=0,int r=0,int id=0,int f=0,int n=0):
        l(l),r(r),id(id),f(f),next(n){}
}Q[max_N<<1];

int head[max_N],tot;

inline void add_QY(int x,int l,int r,int id,int f){
    if(x&&x<=n)Q[++tot]=QY(l,r,id,f,head[x]),head[x]=tot;
}

int st[max_N],top;

int bit1[max_N],bit2[max_N];

ll bit3[max_N];

inline int sum(int x){
    int res=0;
    for(;x;x-=x&-x)res+=bit1[x];
    return res;
}

inline void add(int x,int y){
    if(x>1){
        for(int i=1;i<=n;i+=i&-i)bit2[i]+=y;
        for(int i=x;i<=n;i+=i&-i)bit2[i]-=y;
    }
    for(int i=x;i&&i<=n;i+=i&-i)bit3[i]+=x*y;
}

inline void modify(int l,int r){
    if(l>r)return;
    add(l-1,-1),add(r,1);
}

inline ll query(int x){
    ll res=0;
    for(int i=x;i;i-=i&-i){
        res+=1ll*x*bit2[i]+bit3[i];
    }
    return res;
}

inline ll query(int l,int r){
    return query(r)-query(l-1);
}

int main(){
    n=gint(),m=gint(),p1=gint(),p2=gint();
    for(int i=1;i<=n;++i)k[i]=gint();
    for(int i=1;i<=m;++i){
        l[i]=gint(),r[i]=gint();
        add_QY(l[i]-1,l[i],r[i],i,-1),add_QY(r[i],l[i],r[i],i,1);
    }
    st[top=0]=0;
    for(int x=1;x<=n;++x){
        while(top&&k[st[top]]<k[x])--top;
        for(int i=st[top];i&&i<=n;i+=i&-i)++bit1[i];
        modify(st[top]+1,x-1);
        for(int i=head[x];i;i=Q[i].next){
            cnt1[Q[i].id]+=(sum(Q[i].r)-sum(Q[i].l-1))*Q[i].f;
            cnt2[Q[i].id]+=query(Q[i].l,Q[i].r)*Q[i].f;
        }
        st[++top]=x;
    }
    tot=0;
    for(int i=1;i<=n;++i)head[i]=bit1[i]=bit2[i]=bit3[i]=0;
    for(int i=1;i<=m;++i){
        add_QY(l[i],l[i],r[i],i,1),add_QY(r[i]+1,l[i],r[i],i,-1);
    }
    st[top=0]=n+1;
    for(int x=n;x;--x){
        while(top&&k[st[top]]<k[x])--top;
        for(int i=st[top];i&&i<=n;i+=i&-i)++bit1[i];
        modify(x+1,st[top]-1);
        for(int i=head[x];i;i=Q[i].next){
            cnt1[Q[i].id]+=(sum(Q[i].r)-sum(Q[i].l-1))*Q[i].f;
            cnt2[Q[i].id]+=query(Q[i].l,Q[i].r)*Q[i].f;
        }
        st[++top]=x;
    }
    for(int i=1;i<=m;++i){
        printf("%lld\n",cnt1[i]*p1+(cnt2[i]-cnt1[i])*p2);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值