Time Limit: 20 Sec Memory Limit: 512 MB
Submit: 1412 Solved: 663
给定长度为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
好颓废啊,一上午就写了这么一道题,真颓废
一看是一道莫队题,貌似很水的样子,然后也就只看出了是道莫队题,然后就不会转移了
于是看了题解%%%
我再叙述一遍,顺带复习
首先我们发现如果对 [l,r] 转移到 [l−1,r] 或者 [l,r+1] 貌似只会增加 r−l+1 个区间来计算答案
那么我们如果每次都去 for 一遍区间,显然非常不优,那么怎么办呢
我们用 lefti 表示以 i 作为最小值可以到的最左边区间的端点,换句话说, i 的左边第一个小于其的数的位置
那么如果令 dp[i][j] 表示从 i 这个位置一直往左走到 j 位的以 i 为右端点的所有区间的答案
那么显然有 dp[i][j]=dp[lefti][j]+(i−lefti)∗a[i]
就是说一直到 lefti+1 ,即 [lefti+1,j] 这个区间的所有以 j 结尾的区间的最小值都是 a[i]
那么怎么计算 dp[lefti][j] 呢,显然我们可以计算下一个比 lefti 位置的值更小的值,比如这个位置是 loc
那么区间 [loc+1,lefti] ,即所有左端点在这个区间里的,右端点为 j 的区间的答案都是 a[lefti]
这段区间的贡献是 (lefti−loc)∗a[lefti]
于是我们一直跳,一直到找到左端点为 i 为止
这就相当于一棵树的结构,我们从右边的一个较小值的位置连到他左边的第一个比他更小的值
边权为区间长度乘以右边的那个较小值
于是我们维护一个 up 数组和 down 数组分别表示这棵”树”中,这个点到这棵”树的根”的边权和
就好像我们要求树上两个点的距离,两个点中浅的一个点在深的一个点到根的路径上时
维护两个点到根节点的距离然后相减一样,所以这是有区间相减的性质的
所以我们维护一个 st 表,查询区间最小值,查询到区间最小值之后去求这个最小值到我们要求的位置的值
long long call(int l,int r){//考虑左端点对l到r区间的影响
int loc=find(l,r);//find为找区间l,r里最小的那个值的位置(在原来的数列中)
return A[loc]*(r-loc+1)+up[l]-up[loc];
}
如上所示是一个求 l 对右端点在 [l,r] 里的所有区间对答案的贡献的函数,我们是先求得一个最小值
然后再用这个最小值去求右边区间的贡献和左边的 O(1) 查询贡献
然而为什么我们需要一个 st 表呢?
注意到我们的 r 这个位置
他是否一定是从 l 这个位置一直往右连边所能连到的点呢(即不断往右找第一个比其小的数)
好像不一定吧 233 ,所以说,如果没有连上的话,显然是不能直接计算的啦
然而我们还是可以直接找一下 [l,r] 区间内的最小值,然后去从这个最小值转移
因为从左端点到右端点的最小值都已经找到了,那么从左端点往右连的边中,不经过这个最小值说不过去了吧
最后一个问题: up 数组和 down 数组怎么求?我们可以用单调栈来求,这里以求 down 数组为例
因为 down 数组是求以i为结尾的一直到”树根”的答案,所以我们每次就先把单调栈里的数 pop
pop 的原则是单调栈里的元素大于当前的元素,直到小于当前的元素或者 pop 空
此时单调栈里存的也就是第一个小于当前位置的数的位置了,处理完之后把新的数 push 进去
由于新的数更新,所以之后的数如果找到这个数的话,那么一定是最近的比其小的数
(好像这几句话都是很基础的东西 233 ,是因为我菜啦)
int top=0;
for(register int i=1;i<=n;i++){
while(top&&A[i]<=A[sta[top]])top--;//单增的栈,处理以i结尾
down[i]=down[sta[top]]+A[i]*(i-sta[top]);//栈内存的是一个位置,第一个比当前位置小的位置,而这个位置是一定已经被处理过的
sta[++top]=i;//down存的是以i结尾的一直到根的值
}
#include
using namespace std;
const int inf=0x3f3f3f3f;
const int MAXN=100000+10;
struct Q{int l,r,blo,id;long long ans;}q[MAXN];
int n,m,sta[MAXN],dp[MAXN<<1][20];
long long A[MAXN],down[MAXN],up[MAXN];
bool cmp(const Q &A,const Q &B){
if(A.blo!=B.blo) return A.bloif(A.r!=B.r) return A.rreturn A.lint top=0;
for(register int i=1;i<=n;i++){
while(top&&A[i]<=A[sta[top]])top--;
down[i]=down[sta[top]]+A[i]*(i-sta[top]);
sta[++top]=i;
}
top=0;sta[top]=n+1;
for(register int i=n;i;i--){
while(top&&A[i]<=A[sta[top]])top--;
up[i]=up[sta[top]]+A[i]*(sta[top]-i);
sta[++top]=i;
}
}
int mn(int x,int y){
if(A[x]y]) return x;
else return y;
}
void get_st(){
A[0]=inf;
for(register int i=1;i<=n;i++) dp[i][0]=i;
for(register int j=1;(1<for(register int i=1;i+(1<1<=n;i++)
dp[i][j]=mn(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int find(int L,int R){
int M=0;while((1<<(M+1))<(R-L+1)) M++;
return mn(dp[L][M],dp[R-(1<1][M]);
}
long long call(int l,int r){
int loc=find(l,r);
return A[loc]*(r-loc+1)+up[l]-up[loc];
}
long long calr(int l,int r){
int loc=find(l,r);
return A[loc]*(loc-l+1)+down[r]-down[loc];
}
void Mos_Algorithm(){
sort(q+1,q+m+1,cmp);
int l=1,r=0;
long long tmp=0;
for(register int i=1;i<=m;i++){
while(r<q[i].r) tmp+=calr(l,++r);
while(r>q[i].r) tmp-=calr(l,r--);
while(l>q[i].l) tmp+=call(--l,r);
while(l<q[i].l) tmp-=call(l++,r);
q[q[i].id].ans=tmp;
}
for(register int i=1;i<=m;i++) printf("%lld\n",q[i].ans);
}
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++) scanf("%lld",&A[i]);
init();get_st();
int block=sqrt(n);
for(register int i=1;i<=m;i++){scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;q[i].blo=(q[i].l-1)/block+1;}
Mos_Algorithm();
return 0;
}
/*
5 5
5 2 4 1 3
1 5
1 3
2 4
3 5
2 5
*/