1. 程式人生 > >[bzoj4540][Hnoi2016]序列——單調棧+莫隊+RMQ

[bzoj4540][Hnoi2016]序列——單調棧+莫隊+RMQ

題目大意:

給定一個序列,每次詢問一個區間[L,R]所有子區間的最小值之和。

思路:

考慮莫隊如何轉移,新增一個端點R,則增加的區間為[L...R-1,R],考慮這些區間新貢獻的最小值。
我們把從R開始向左單調下降的序列給求出來,不難發現最小值是由區間內包含的最靠左一個在單調下降序列裡的元素的值所決定的。
於是我們利用單調棧求出每一個元素前面第一個小於它的元素\(pre_i\),並求出以這個元素結尾的所有區間的最小值的和\(f_i\),不難發現\(f_i=f_{pre_i}+(i-pre_i)\times a_i\),遞推求出來\(f\)陣列之後我們要求新增加一個點的貢獻,只需要找出區間內最小的點的位置\(m\)

,對於左端點在m之前的,最小值都是\(a_m\),區間左端點在\([m+1,R]\)的部分我們可以用\(f_i-f_m\)得到,這樣就可以用RMQ\(O(1)\)轉移了。
但是這樣還不夠優秀,上面的做法提示我們,如果要求\(\sum_{i=L}^{R}min(a_i..a_R)\)這個東西,可以用RMQ求出最小值的位置之後再利用\(f\)陣列相減來解決。
考慮對於一個區間[L,R],最小值的位置為\(m\),那麼顯然對於\([m+1,R]\)區間的所有點當它們為右端點時字首最小值為\(a_m\),所以通過\(\sum_{i=m+1}^{R}(f_i-f_m)\)可以得到左右端點都在[m+1,R]的子區間的權值和,同理可得左右端點都在[L,m-1]的子區間的權值和,對於包含m點的區間可以直接通過\((m-L+1)\times (R-m+1)\times a_m\)
計算出。
於是得到了這一題較優秀的做法,時間複雜度\(O(n\log n +q)\)

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define MREP(i,x) for(int i=beg[x],v;v=to[i],i;i=las[i])
#define debug(x) cout<<#x<<"="<<x<<endl
#define pii pair<ll,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
    freopen("bzoj4540.in","r",stdin);
    freopen("bzoj4540.out","w",stdout);
}

template<typename T>void read(T &_){
    T __=0,mul=1; char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')mul=-1;
        ch=getchar();
    }
    while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
    _=__*mul;
}

const int maxn=1e5+10;
int n,m,Log[maxn];
ll a[maxn],f1[maxn],g1[maxn],f2[maxn],g2[maxn];
pii st[maxn][21];

int query(int l,int r){
    int d=Log[r-l+1];
    return min(st[l][d],st[r-(1<<d)+1][d]).se;
}

void init(){
    read(n); read(m);
    REP(i,1,n)read(a[i]);

    REP(i,2,n)Log[i]=Log[i/2]+1;
    REP(i,1,n)st[i][0]=mk(a[i],i);
    REP(j,1,20)REP(i,1,n-(1<<j)+1)
        st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);

    int tp;
    ll s[maxn];

    tp=0;
    REP(i,1,n){
        while(tp && a[s[tp]]>a[i])--tp;
        f1[i]=f1[s[tp]]+(i-s[tp])*a[i];
        s[++tp]=i; g1[i]=f1[i]+g1[i-1];
    }
    s[tp=0]=n+1;
    DREP(i,n,1){
        while(tp && a[s[tp]]>a[i])--tp;
        f2[i]=f2[s[tp]]+(s[tp]-i)*a[i];
        s[++tp]=i; g2[i]=f2[i]+g2[i+1];
    }
}

void work(){
    int l,r,p;
    ll ans;
    REP(i,1,m){
        read(l); read(r);
        p=query(l,r);
        ans=a[p]*(p-l+1)*(r-p+1);
        ans+=g1[r]-g1[p]-(r-p)*f1[p];
        ans+=g2[l]-g2[p]-(p-l)*f2[p];
        printf("%lld\n",ans);
    }
}

int main(){
    File();
    init();
    work();
    return 0;
}