题目描述
给定长度为的序列:
,记为
。类似地,
是指序列:
。若
,则称
是
的子序列。
现在有个询问,每个询问给定两个数
和
,
,求
的不同子序列的最小值之和。
例如,给定序列,询问给定的两个数为
和
,那么
有
个子序列
,这
个子序列的最小值之和为
。
一个技巧:取最小数
先取区间的最小值,设下标为
首先对于左端点,右端点
的区间,最小值都是
,所以先加上
然后考虑左端点、右端点的区间和左端点、右端点
的区间
我们处理一个数组 _
表示以
为右端点,左端点
的区间最小值之和
设第一个比第个元素小的数的下标为
显然 _
_
又处理一个 _
表示
_
那么,左端点、右端点的区间,贡献为
_
_
_
想一想,为什么
首先 _
_
很好理解,只算右端点在
的区间
也就是说,后面一项是将右端点在的区间,而左端点在
的区间的贡献减掉
我们发现,这相当于每一个 _
减去了
_
那为什么可以减呢,这本是不可以减的
注意到开头的是
的最小值,再回到算
_
的公式
我们发现,无论是哪个,经过若干次
的迭代后,一定会变成
_
_
_
_
减去之后,恰好是右端点为,左端点在
区间的权值和
左半边贡献同理
于是就做完了
#include <bits/stdc++.h>
#define ri int
#define ll long long
using namespace std;
const int Maxn=1e5;
const int Lgn=17;
stack<int>s;
int pre[Maxn+5],suf[Maxn+5],n,m;
int st[Maxn+5][Lgn+5],lg[Maxn+5];
ll f_pre[Maxn+5],g_pre[Maxn+5],f_suf[Maxn+5],g_suf[Maxn+5],a[Maxn+5];
int query(int l,int r) {
int t=lg[r-l+1];
if(a[st[l][t]]<a[st[r-(1<<t)+1][t]])return st[l][t];
return st[r-(1<<t)+1][t];
}
int main() {
scanf("%d%d",&n,&m);
lg[0]=-1;
for(ri i=1;i<=n;i++)lg[i]=lg[i>>1]+1;
for(ri i=1;i<=n;i++)scanf("%lld",&a[i]),st[i][0]=i;
a[0]=a[n+1]=-1e9-1;
for(ri j=1;j<=lg[n];j++)
for(ri i=1;i<=n-(1<<j)+1;i++) {
int x=st[i][j-1],y=st[i+(1<<(j-1))][j-1];
if(a[x]<a[y])st[i][j]=x;
else st[i][j]=y;
}
s.push(0);
for(ri i=1;i<=n;i++) {
while(!s.empty()&&a[s.top()]>a[i])s.pop();
pre[i]=s.top();
s.push(i);
}
while(!s.empty())s.pop();
s.push(n+1);
for(ri i=n;i>=1;i--) {
while(!s.empty()&&a[s.top()]>a[i])s.pop();
suf[i]=s.top();
s.push(i);
}
for(ri i=1;i<=n;i++) {
f_pre[i]=f_pre[pre[i]]+a[i]*(i-pre[i]);
g_pre[i]=g_pre[i-1]+f_pre[i];
}
for(ri i=n;i>=1;i--) {
f_suf[i]=f_suf[suf[i]]+a[i]*(suf[i]-i);
g_suf[i]=g_suf[i+1]+f_suf[i];
}
while(m--) {
int l,r;
scanf("%d%d",&l,&r);
int x=query(l,r);
printf("%lld\n",a[x]*(r-x+1)*(x-l+1)+g_pre[r]-g_pre[x]-f_pre[x]*(r-x)+g_suf[l]-g_suf[x]-f_suf[x]*(x-l));
}
return 0;
}

京公网安备 11010502036488号