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;
}

根据引用\[1\]和引用\[2\]的描述,题目的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值