题目描述
给定长度为的序列:
,记为
。类似地,
是指序列:
。若
,则称
是
的子序列。
现在有个询问,每个询问给定两个数
和
,
,求
的不同子序列的最小值之和。
例如,给定序列,询问给定的两个数为
和
,那么
有
个子序列
,这
个子序列的最小值之和为
。
一个技巧:取最小数
先取区间的最小值,设下标为
首先对于左端点,右端点
的区间,最小值都是
,所以先加上
然后考虑左端点、右端点的区间和左端点、右端点
的区间
我们处理一个数组 _
表示以
为右端点,左端点
的区间最小值之和
设第一个比第个元素小的数的下标为
显然 _
_
又处理一个 _
表示
_
那么,左端点、右端点的区间,贡献为
_
_
_
想一想,为什么
首先 _
_
很好理解,只算右端点在
的区间
也就是说,后面一项是将右端点在的区间,而左端点在
的区间的贡献减掉
我们发现,这相当于每一个 _
减去了
_
那为什么可以减呢,这本是不可以减的
注意到开头的是
的最小值,再回到算
_
的公式
我们发现,无论是哪个,经过若干次
的迭代后,一定会变成
_
_
_
_
减去之后,恰好是右端点为,左端点在
区间的权值和
左半边贡献同理
于是就做完了
#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; }