loj #2051. 「HNOI2016」序列(莫队+ST+单调栈)

题目链接

#2051. 「HNOI2016」序列

l,r 所有子集最小值和

分析

又是区间查询,上莫队了.首先想[l,r]-> [l,r+1] 它的增量是多少.
设[l,r+1] 的最小值的位置是 p , 那么显然增加的贡献为 a[p](pl+1)+(r+1)

sl[i] 表示以 i 为右端点的所有区间的贡献前缀和,那么显然当前区间中 r+1 为右端点的贡献为 sl[r+1]sl[p]

l[i] : 左边第一个比a[i] 严格小的位置
r[i] : 右边第一个小于等于a[i] 的位置

那么显然

sl[i]=sl[l[i]]+a[i](il[i])
因此用单调栈预处理 l[i],r[i] ,然后用ST表查询最小值的位置就好了

中途WA了很多次,原来莫队更新的顺序很重要,特别是与区间相关,点不独立的问题

AC code

#include <bits/stdc++.h>
using namespace std;
#define ms(x,v) (memset((x),(v),sizeof(x)))
#define pb push_back
#define mp make_pair
#define fi first
#define se second
typedef long long LL;
typedef pair<int,int > Pair;
const int maxn = 1e5+10;
int n,m;
LL a[maxn];
const int S = 300;
struct Query{
    int l,r,id;
    bool operator < (const Query & o)const{
        return l/S == o.l/S?r < o.r:l<o.l;
    }
};
Query q[maxn];
int l[maxn],r[maxn],stk[maxn],top;
LL sl[maxn],sr[maxn],ret[maxn];
void init() {
    top=0;
    for(int i=1 ; i<=n ; ++i){
        while (top && a[stk[top]] >=a[i])r[stk[top--]] =i;//被右端第一个小于等于它的元素驱逐出栈
        stk[++top] =i;
    }
    top=0;
    for(int i=n ; i>0 ; --i){
        while (top && a[stk[top]] > a[i])l[stk[top--]] = i;
        stk[++top] = i;
    }
    sl[0] = 0;sr[n+1] = 0;l[1] =0;r[n] = n+1;
    for(int i=1 ; i<=n ; ++i)sl[i] = sl[l[i]]+(LL)a[i]*(i-l[i]);
    for(int i=n ; i>0 ; --i)sr[i] = sr[r[i]] + (LL)a[i]*(r[i]-i);
}
namespace ST{
    int st[maxn][22],lg2[maxn];
    void init_rmq(int n,LL a[]) {
        lg2[0] =-1;
        for(int i=1 ; i<=n ; ++i){
            lg2[i] = ((i&(i-1)) == 0)?lg2[i-1]+1:lg2[i-1];
            st[i][0] =i;
        }
        for(int j=1 ; j<=lg2[n]; ++j)
            for(int i=1 ; i+(1<<j)-1 <=n ; ++i)
                st[i][j] = (a[st[i][j-1]]<a[st[i+(1<<(j-1))][j-1]])?st[i][j-1]:st[i+(1<<(j-1))][j-1];
    }
    int rmq(int l,int r,LL a[]){
        int k = lg2[r-l+1];
        return a[st[l][k]] < a[st[r-(1<<k)+1][k]]?st[l][k]:st[r-(1<<k)+1][k];
    }
};
using namespace ST;
inline LL moveL(int l,int r) {
    int p = rmq(l,r,a);
    return sr[l]-sr[p]+a[p]*(r-p+1);
}
inline LL moveR(int l,int r){
    int p = rmq(l,r,a);
    return sl[r] - sl[p]+a[p]*(p-l+1);
}
int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    for(int i=1 ; i<=n ; ++i)cin>>a[i];
    init_rmq(n,a);
    init();
    for(int i=0 ; i<m ; ++i){
        q[i].id = i;cin>>q[i].l >> q[i].r;
    }
    sort(q,q+m);
    int curL=q[0].l,curR = q[0].l-1;
    LL ans =0;
    for(int i=0 ; i<m ; ++i){
        int L = q[i].l,R = q[i].r;
        while (curR < R)ans += moveR(curL,++curR);
        while (curL > L)ans += moveL(--curL,curR);
        while (curR > R)ans -= moveR(curL,curR--);
        while (curL < L)ans -=moveL(curL++,curR);
        ret[q[i].id] = ans;
    }
    for(int i=0 ; i<m ; ++i)std::cout << ret[i] << '\n';
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值