【题解】HNOI-2016序列

Problem

collapse_bzoj

Solution

这道题在HNOI2016中还算是好的了……

这题中如若去掉多组询问的话可以在 O(nlogn) O ( n log ⁡ n ) 的时间内得解(并查集),但多组询问必定要优化,发现这种其他结构基本上无法涉足的题目就只能上莫队了(我也不知道为啥想到莫队,可能这就是题感吧)

减去 O(nn) O ( n n ) 只剩下常数时间可供转移了,那么就要想法常数转移

明显在一个序列变化时答案的变化值取决于边界消长节点与序列中每一个节点所组成区间的答案,那么一定只有预处理才能常数解决

考虑一个数 ai a i 只有在前面第一个比其小的元素后面,后面第一个比其小的元素前面的区间才有效,那么预处理出 pre[] p r e [ ] nxt[] n x t [ ] 数组分别表示每个元素前后第一个比其小的元素的位置,明显可以用单调栈 O(n) O ( n ) 解决(不会的看代码),再根据这两个数组预处理出一个贡献前缀和与一个贡献后缀和,那么转移的大体贡献就可以根据这两个前/后缀和做差得到

剩下的就只有区间的边缘部分,边缘部分只用快速查找边缘部分中的最小值即可,如果也要用常数时间查询,蒟蒻就只知道st表了

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rg register
#define cl(x) memset(x,0,sizeof(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define abs(x) ((x)>0?(x):(-(x)))

template <typename _Tp> inline _Tp read(_Tp&x){
    rg char c11=getchar(),ob=0;x=0;
    while(c11^'-'&&!isdigit(c11))c11=getchar();if(c11=='-')c11=getchar(),ob=1;
    while(isdigit(c11))x=x*10+c11-'0',c11=getchar();if(ob)x=-x;return x;
}

const int N=100100;
struct Query{int l,r,id;}qu[N];
ll a[N],suml[N],sumr[N],Ans[N];
int st[N][18],Log[N],pre[N],nxt[N],sta[N],top(0);
int n,m;

inline int cmp1(const Query&aa,const Query&bb){return aa.l<bb.l;}
inline int cmp2(const Query&aa,const Query&bb){return aa.r<bb.r;}

inline int query(int l,int r){
    #define get_(x,y) (a[x]<a[y]?(x):(y))
    return get_(st[l][Log[r-l+1]],st[r-(1<<Log[r-l+1])+1][Log[r-l+1]]);
    #undef get_
}

void init();

void pre_pre_suf();

void pre_st();

void pre_pointer();

void div_block();

void Captain_Mo(){
    rg int l(1),r(1),pos;rg ll ans(a[1]);
    for(rg int i=1;i<=m;++i){
        while(r<qu[i].r){++r;pos=query(l,r);ans+=a[pos]*(pos-l+1)+suml[r]-suml[pos];}
        while(qu[i].l<l){--l;pos=query(l,r);ans+=a[pos]*(r-pos+1)+sumr[l]-sumr[pos];}
        while(qu[i].r<r){pos=query(l,r);ans-=a[pos]*(pos-l+1)+suml[r]-suml[pos];--r;}
        while(l<qu[i].l){pos=query(l,r);ans-=a[pos]*(r-pos+1)+sumr[l]-sumr[pos];++l;}
        Ans[qu[i].id]=ans;
    }
    return ;
}

void Print();

int main(){
    init();
    pre_pointer();
    pre_st();
    pre_pre_suf();
    div_block();
    Captain_Mo();
    Print();
    return 0;
}

void pre_pre_suf(){
    for(rg int i=1;i<=n;++i)suml[i]=suml[pre[i]]+(i-pre[i])*a[i];
    for(rg int i=n;i;--i)   sumr[i]=sumr[nxt[i]]+(nxt[i]-i)*a[i];
    return ;
}

void Print(){for(rg int i=1;i<=m;++i)printf("%lld\n",Ans[i]);return ;}

void div_block(){
    sort(qu+1,qu+m+1,cmp1);
    int block=sqrt(1.0*m);
    for(rg int i=1;i<=m;i+=block)
        sort(qu+i,qu+min(i+block,m),cmp2);
    return ;
}

void pre_st(){
    #define get_(x,y) (a[(x)]<a[(y)]?(x):(y))
    Log[0]=-1;
    for(rg int i=1;i<=n;++i) Log[i]=Log[i>>1]+1,st[i][0]=i;

    for(rg int i=n;i;--i)
    for(rg int j=1;i+(1<<j)-1<=n;++j)
        st[i][j]=get_(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    return ;
    #undef get_
}

void pre_pointer(){
    for(rg int i=1;i<=n;++i){
        while(top&&a[sta[top]]>a[i])nxt[sta[top]]=i,--top;
        pre[i]=sta[top];
        sta[++top]=i;
    }
    return ;
}

void init(){
    read(n),read(m);
    for(rg int i=1;i<=n;++i)read(a[i]);
    for(rg int i=1;i<=m;++i)read(qu[i].l),read(qu[i].r),qu[i].id=i;
    return ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值