题目描述
给定长度为n的序列:a1,a2,...,an,记为a[1:n]。类似地,a[l:r](1<=l<=r<=N)是指序列:al,al+1,...,ar-1,ar。若1<=l<=s<=t<=r<=n,则称a[s:t]是a[l:r]的子序列。现在有q个询问,每个询问给定两个数l和r,1<=l<=r<=n,求a[l:r]的子序列的最小值之和。例如,给定序列5,2,4,1,3,询问给定的两个数为1和3,那么a[1:3]有6个子序列a[1:1],a[2:2],a[3:3],a[1:2],a[2:3],a[1:3],这6个子序列的最小值之和为5+2+4+2+2+2=17。
输入输出格式
输入格式:输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数。接下来一行,包含n个整数,以空格隔开,第i个整数为ai,即序列第i个元素的值。接下来q行,每行包含两个整数l和r,代表一次询问。
对于每次询问,输出一行,代表询问的答案。
输入输出样例
5 5 5 2 4 1 3 1 5 1 3 2 4 3 5 2 5
28 17 11 11 17
说明
1 <=N,Q <= 100000,|Ai| <= 10^9
原题40%数据是 ST表+二维前缀和,另外20%数据是ST表+分治,这里说100%数据:
ST表+单调栈+莫队
无论如何都要有ST表,这个ST表求的是最小值的下标。
单调栈预处理每个数向左右能成为多大区间的最小值。
然后就是莫队了。。。
莫队的难点就在于状态转移,怎么通过[l,r]的答案推出[l,r+1]的答案。
考虑找到[l,r+1]里的最小值的位置(ST表),记为pos。
[l,r+1]相对于[l,r]其实就多了r-l+2个包括r+1的子序列,
那么[l,pos]的对答案的贡献就等于(pos-l+1)*a[pos]
那么怎么求[pos+1,r]的贡献呢
这里引入一个类似前缀和的东西,
我们用 front[i] 表示a[i]左边第一个比a[i]小的数的位置,next[i] 表示右边,
这个过程显然可以用单调栈O(n)实现
然后开2个数组maxl , maxr。
maxl[i] 表示 [1,i] 的贡献,maxl[i] 可通过maxl[i] = maxl[ front[i] ] + ( i-front[i] ) * val[i]得到。
同理,maxr[i] = maxr[ next[i] ] + ( next[i]-i ) * val[i]。
注意:记得开long long。。。
附代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
#define MAXN 100010
using namespace std;
int n,m;
int val[MAXN],f[MAXN][30],front[MAXN],next[MAXN];
long long ans[MAXN],maxl[MAXN],maxr[MAXN];
struct node{
int l,r,id;
}que[MAXN];
struct Stack{
int value[MAXN],numtop;
Stack(){
memset(value,0,sizeof(value));
numtop=0;
}
inline void push(int x){value[++numtop]=x;}
inline void pop(){value[numtop--]=0;}
inline int top(){return value[numtop];}
inline int empty(){return (numtop==0?1:0);}
}stack;
inline int read(){
int date=0,w=1;char c=0;
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
return date*w;
}
bool cmp1(const node &x,const node &y){
return x.l<y.l;
}
bool cmp2(const node &x,const node &y){
if(x.r==y.r)return x.l<y.l;
return x.r<y.r;
}
void step(){
for(int i=1;(1<<i)<=n;i++)
for(int j=1;j+(1<<i)-1<=n;j++){
if(val[f[j][i-1]]>val[f[j+(1<<(i-1))][i-1]])f[j][i]=f[j+(1<<(i-1))][i-1];
else f[j][i]=f[j][i-1];
}
}
int query(int l,int r){
int k=0;
while((1<<(k+1))<=r-l+1)k++;
if(val[f[l][k]]>val[f[r-(1<<k)+1][k]])return f[r-(1<<k)+1][k];
return f[l][k];
}
void work(){
int left=1,right=0;
long long s=0;
for(int i=1;i<=m;i++){
while(right<que[i].r)
{
right++;
int pos=query(left,right);
s+=(long long)val[pos]*(pos-left+1)+maxl[right]-maxl[pos];
}
while(que[i].l>left)
{
int pos=query(left,right);
s-=(long long)val[pos]*(right-pos+1)+maxr[left]-maxr[pos];
left++;
}
while(right>que[i].r)
{
int pos=query(left,right);
s-=(long long)val[pos]*(pos-left+1)+maxl[right]-maxl[pos];
right--;
}
while(que[i].l<left)
{
left--;
int pos=query(left,right);
s+=(long long)val[pos]*(right-pos+1)+maxr[left]-maxr[pos];
}
ans[que[i].id]=s;
}
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
}
void init(){
int nowi=1,nowj=0,d,x;
n=read();m=read();
for(int i=1;i<=n;i++){
val[i]=read();
f[i][0]=i;
}
step();
for(int i=1;i<=m;i++){
que[i].l=read();que[i].r=read();
que[i].id=i;
}
x=sqrt(n);
sort(que+1,que+m+1,cmp1);
while(nowi<=m){
nowj++;
d=nowi;
while(que[nowi].l<nowj*x&&nowi<=m)nowi++;
sort(que+d,que+nowi,cmp2);
if(nowj==x){
sort(que+d,que+m+1,cmp2);
break;
}
}
for(int i=1;i<=n;i++){
for(;!stack.empty()&&val[i]<val[stack.top()];stack.pop())next[stack.top()]=i;
front[i]=stack.top();
stack.push(i);
}
for(;!stack.empty();stack.pop())next[stack.top()]=n+1;
for(int i=1;i<=n;i++)maxl[i]=maxl[front[i]]+(long long)val[i]*(i-front[i]);
for(int i=n;i>=1;i--)maxr[i]=maxr[next[i]]+(long long)val[i]*(next[i]-i);
}
int main(){
init();
work();
return 0;
}