码迷,mamicode.com
首页 > 其他好文 > 详细

[bzoj4540][Hnoi2016]序列——单调栈+莫队+RMQ

时间:2018-12-09 11:57:05      阅读:224      评论:0      收藏:0      [点我收藏+]

标签:def   make   计算   for   names   end   pre   hnoi   数组   

题目大意:

给定一个序列,每次询问一个区间[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;
}

[bzoj4540][Hnoi2016]序列——单调栈+莫队+RMQ

标签:def   make   计算   for   names   end   pre   hnoi   数组   

原文地址:https://www.cnblogs.com/ylsoi/p/10090222.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!