P3246 [HNOI2016]序列(查询l-r中所有区间的最小值之和)

Posted 昵称很长很长真是太好了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3246 [HNOI2016]序列(查询l-r中所有区间的最小值之和)相关的知识,希望对你有一定的参考价值。

多校时做到了查询区间l-r中所有区间的最大值与最小之和的题目,有好多细节不太会处理,去看题解发现是一道差不多的原题,于是打算先把原题补一下。
题解:
ST表+单调栈+莫队
看到计算区间最小值之和,不难想到可能会与单调栈有关系,再想了一下,应该是需要计算每个数对这个区间做了多少贡献。然后看到离线操作还可以去思考莫队,但是很遗憾,想到这里思路就没太有了。去翻了翻题解恍然大悟。
我们考虑莫队的转移,如果我们能 O ( 1 ) O(1) O(1)求出左端点对整个区间的贡献,和右端点对区间的贡献,那么就可以莫队了
首先我们可以看假设l-r之间的区间序列为:
5 3 8 7 1 2 6 5
我们尝试计算左端点对整个区间的贡献。
5 3 3 3 1 1 1 1
我们发现以上序列和即为贡献。
我们可以发现一个规律,就是越往右边,数会越小,那我们先找到区间最小值的位置,然后用区间最小值乘(r-最小值位置)那么就是1 1 1 1对序列做的贡献。
5 3 3 3这个序列应该怎么求呢?
r [ i ] r[i] r[i]为当前这个数右边第一个比这个数小的位置。
我们对单调栈做一个后缀和, s u f [ i ] = s u f [ r [ i ] ] + a [ i ] ∗ ( r [ i ] − i ) suf[i]=suf[ r[i] ]+a[i]*(r[i]-i) suf[i]=suf[r[i]]+a[i](r[i]i)
设imin为l-r之间的最小值下标
那么左边这一段计算方法即为 s u f [ l ] − s u f [ i m i n ] suf[l]-suf[imin] suf[l]suf[imin],看起来好像不太正确的样子,但他确实是正确的,为什么呢?因为我们 i m i n imin imin为这个区间最小值的位置了,那么当这个后缀经过 i m i n imin imin后,他修改值不会修改到 i m i n imin imin右边的那些值。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int maxn = 5e5+10;

int n,m;
int a[maxn];
int anshou[maxn],ansqian[maxn];
int pre[maxn],suf[maxn],pos[maxn];
void init(){
    stack<int> s;
    for(int i=n;i>=1;i--){
        while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
        anshou[i]=s.empty()?n+1:s.top();
        s.push(i);
    }
    while(s.size()) s.pop();
    for(int i=1;i<=n;i++){
        while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
        ansqian[i]=s.empty()?0:s.top();
        s.push(i);
    }
    for(int i=1;i<=n;i++){
        pre[i]=pre[ansqian[i]]+a[i]*(i-ansqian[i]);
    }
    for(int i=n;i>=1;i--){
        suf[i]=suf[anshou[i]]+a[i]*(anshou[i]-i);
    }
}

int stmin[maxn][21],mn[maxn];


int stcmp(int x,int y){
	return a[x]<a[y]?x:y;
}
void init_st(){
    mn[0]=-1;
    for (int i=1;i<=n;i++){
        mn[i]=((i & (i-1))==0) ? mn[i-1]+1 : mn[i-1];
        stmin[i][0]=i;
    }
    for(int j=1;j<=mn[n];j++)
        for(int i=1;i+(1<<j)-1<=n;i++){
            stmin[i][j]=stcmp(stmin[i][j-1],stmin[i+(1<<(j-1))][j-1]);
        }
}
inline int rmq_min(int L,int R){
    int k=mn[R-L+1];
    return stcmp(stmin[L][k],stmin[R-(1<<k)+1][k]);
}

struct Q{
    int l,r,k;
}q[maxn];

struct rule{
    bool operator ()(const Q & a, const Q & b)const{
    if(pos[a.l]!=pos[b.l]) return a.l<b.l;
    if(pos[a.l]&1) return a.r<b.r;
    return a.r>b.r;   //因为当l移动到另外一个分块时,r的移动会非常明显。
    }
};
int ans[maxn];

inline int upl(int l,int r){
    int id=rmq_min(l,r);
    return (r-id+1)*a[id]+suf[l]-suf[id];
}
inline int upr(int l,int r){
    int id=rmq_min(l,r);
    return (id-l+1)*a[id]+pre[r]-pre[id];
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    //int n,m;
    cin>>n>>m;
    int sz=sqrt(n);
    for(int i=1;i<=n;i++) cin>>a[i],pos[i]=i/sz;
    init();
    init_st();
    for(int i=1;i<=m;i++){
        cin>>q[i].l>>q[i].r;
        q[i].k=i;
    }

    sort(q+1,q+1+m,rule());
    int l=1,r=0,res=0;
    for(int i=1;i<=m;i++){
        while(q[i].l<l) res+=upl(--l,r);
        while(q[i].r>r) res+=upr(l,++r);
        while(q[i].l>l) res-=upl(l,r),l++;
        while(q[i].r<r) res-=upr(l,r),r--;
        ans[q[i].k]=res;
    }
    for(int i=1;i<=m;i++){
        cout<<ans[i]<<endl;
    }

    return 0;
}

以上是关于P3246 [HNOI2016]序列(查询l-r中所有区间的最小值之和)的主要内容,如果未能解决你的问题,请参考以下文章

洛谷 - P3246 [HNOI2016]序列(莫队+单调栈)

「HNOI2016」序列

[HNOI 2016]序列

bzoj4540HNOI2016序列

bzoj4540: [Hnoi2016]序列

[BZOJ4540][HNOI2016]序列(莫队)