[AH2017/HNOI2017]影魔

题目

好题啊

先把两种贡献翻译成人话

对于\((i,j)\),如果\(a_i,a_j\)恰好是\([i,j]\)之间的最大值和次大值,那么\((i,j)\)产生\(p1\)的贡献

否则对于\((i,j)\)\(a_i\)是最大值或者\(a_j\)是最大值,那么就产生\(p2\)的贡献

哎这个一个最大值一个严格次大值好像有点眼熟啊

这不bzoj原题

根据那道题我们发现的性质,能产生\(p1\)贡献的点对不会超过\(2n\)个,于是我们用单调栈将这些点对都预处理出来,之后树状数组+扫描线就可以知道每个询问有多少个\(p1\)的贡献

现在求出\(p2\)的贡献就好了

我们考虑一个元素往左右两边扩展,扩展到比它大的元素就停止,这些扫到的点显然都会产生贡献,但是这个点对产生的贡献是\(p1\)还是\(p2\)就不好算了

但是我们都已经算出来\(p1\)的点对数量了,只要拿总数量减一下就是\(p2\)点对的数量了

显然每一个位置往左往右扩展到哪里单调栈已经帮我们求好了,所以现在的问题变成了求每一个询问区间内,每一个位置在区间内往左往右扩展的总长度与之和

考虑把这个扩展的总长度变成一个区间加法,于是我们可以把每一个元素的扩展搞成一个三元组\((l,r,k)\),扩展到\([l,r]\),扩展的位置是\(k\)

我们把每个询问拆成两个,之后将这些区间按照\(k\)来排序,线段树+扫描线就可以了

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lowbit(x) ((x)&(-x))
#define LL long long
#define re register
#define maxn 200005
inline int read() {
    char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
struct Seg{int x,y,k;}t[maxn<<1];
struct node{int x,y;}p[maxn<<1];
struct Ask{int l,r,rk;}q[maxn];
struct _Ask{int x,rk,o,l,r;}_q[maxn<<1];
int n,m,p1,p2,top,tot;
int l[maxn],r[maxn],a[maxn],st[maxn],c[maxn];
int ls[maxn<<2],rs[maxn<<2],tag[maxn<<2];LL d[maxn<<2];
int Ans1[maxn];LL Ans2[maxn];
inline int cxp(_Ask A,_Ask B) {return A.x<B.x;}
inline int cmp(Ask A,Ask B) {return A.r<B.r;}
inline int cop(node A,node B) {return A.y<B.y;}
inline void add(int x,int val) {for(re int i=x;i<=n;i+=lowbit(i)) c[i]+=val;}
inline int ask(int x) {int cnt=0;for(re int i=x;i;i-=lowbit(i)) cnt+=c[i];return cnt;}
void build(int x,int y,int i) {
    ls[i]=x,rs[i]=y;
    if(x==y) return;
    int mid=x+y>>1;
    build(x,mid,i<<1),build(mid+1,y,i<<1|1);
}
inline void pushdown(int i) {
    if(!tag[i]) return;
    tag[i<<1]+=tag[i],tag[i<<1|1]+=tag[i];
    d[i<<1]+=tag[i]*(rs[i<<1]-ls[i<<1]+1);
    d[i<<1|1]+=tag[i]*(rs[i<<1|1]-ls[i<<1|1]+1);
    tag[i]=0;
}
void change(int x,int y,int i) {
    if(x<=ls[i]&&y>=rs[i]) {
        tag[i]++;
        d[i]+=rs[i]-ls[i]+1;
        return;
    }
    pushdown(i);
    int mid=ls[i]+rs[i]>>1;
    if(y<=mid) change(x,y,i<<1);
        else if(x>mid) change(x,y,i<<1|1);
            else change(x,y,i<<1|1),change(x,y,i<<1);
    d[i]=d[i<<1|1]+d[i<<1];
}
int query(int x,int y,int i) {
    if(x<=ls[i]&&y>=rs[i]) return d[i];
    pushdown(i);
    int mid=ls[i]+rs[i]>>1;
    if(y<=mid) return query(x,y,i<<1);
    if(x>mid) return query(x,y,i<<1|1);
    return query(x,y,i<<1|1)+query(x,y,i<<1);
}
signed main() {
    n=read(),m=read();p1=read(),p2=read();
    for(re int i=1;i<=n;i++) a[i]=read();
    for(re int i=1;i<=n;i++) {
        while(top&&a[st[top]]<a[i]) r[st[top--]]=i;
        st[++top]=i;
    }
    while(top) r[st[top--]]=n+1;
    for(re int i=n;i;--i) {
        while(top&&a[st[top]]<a[i]) l[st[top--]]=i;
        st[++top]=i;
    }
    while(top) l[st[top--]]=0;
    for(re int i=1;i<=n;i++) {
        if(l[i]) p[++tot]=(node){l[i],i};
        if(r[i]!=n+1) p[++tot]=(node){i,r[i]};
    }
    for(re int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].rk=i;
    std::sort(p+1,p+tot+1,cop),std::sort(q+1,q+m+1,cmp);
    top=1;int now=1;
    for(re int i=1;i<=n;i++) {
        while(now<=tot&&p[now].y<=i) add(p[now].x,1),now++;
        while(top<=m&&q[top].r<=i) 
            Ans1[q[top].rk]=ask(q[top].r)-ask(q[top].l-1),top++;
    }
    tot=0;now=0;build(1,n,1);
    for(re int i=1;i<=n;i++) {
        if(l[i]+1<=i-1) t[++tot]=(Seg){l[i]+1,i-1,i};
        if(i+1<=r[i]-1) t[++tot]=(Seg){i+1,r[i]-1,i};
    }
    for(re int i=1;i<=m;i++) 
        _q[++now]=(_Ask){q[i].l-1,q[i].rk,-1,q[i].l,q[i].r},_q[++now]=(_Ask){q[i].r,q[i].rk,1,q[i].l,q[i].r};
    std::sort(_q+1,_q+now+1,cxp);
    int tmp=1,cnt=1;
    for(re int i=0;i<=n;i++) {
        while(tmp<=tot&&t[tmp].k<=i) change(t[tmp].x,t[tmp].y,1),tmp++;
        while(cnt<=now&&_q[cnt].x<=i) Ans2[_q[cnt].rk]+=_q[cnt].o*query(_q[cnt].l,_q[cnt].r,1),cnt++;
    }
    for(re int i=1;i<=m;i++) 
        printf("%lld\n",(LL)p1*Ans1[i]+(LL)p2*(Ans2[i]-Ans1[i]));
    return 0;
}

转载于:https://www.cnblogs.com/asuldb/p/10453033.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值