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中所有区间的最小值之和)的主要内容,如果未能解决你的问题,请参考以下文章