题意:给定n(n<=500000)个数,A[1],A[2],...,A[n]。求所有子区间的 (最大值*最小值*长度)之和,对 10^9 取余数。
思路一:暴力 时间复杂度O(n^3) 超时
枚举所有子区间,然后遍历一遍来求最大值和最小值,然后累加答案。
思路二:稍加优化 时间复杂度O(n^2) 超时
枚举R,然后对于每个L<=R,用Max[L]表示区间[L,R]的最大值,Min[L]表示区间[L..R]的最小值。
在R增加1之后,每个L<=旧R的Max[L]和Min[L]只需要用A[R]去更新就行了。
代码:
//Simple solve
int Min[maxn],Max[maxn];
int SimpleSolve(){
LL ANS=0;
for(int R=1;R <=n;++R){
Min[R]=Max[R]=A[R];
for(int L=1;L <= R;++L){
//更新Min[L]和Max[L]
Min[L]=min(Min[L],A[R]);
Max[L]=max(Max[L],A[R]);
//累加区间[L,R]的答案
ANS=(ANS+(LL)Min[L]*Max[L]%MOD*(R-L+1))%MOD;
}
}
return ANS;
}
思路三是思路二的线段树优化,所以在看思路三之前,请确保看懂了思路二。
思路二中,对于每个R,用A[R]更新了[1..R]的最大值和最小值,然后累加了[1..R]到R的答案。
如果更新和求和都使用线段树的话,时间复杂度就变成了O(n*log(n))。
首先,如何用线段树维护最大值。
对于每个R,要将Max[1..R]的数组中,所有小于A[R]的都更新成A[R]。
注意到Max[1],Max[2],...,Max[R]是单调非增的数列。
(Max[1]代表区间[1..R]的最大值,Max[2]代表区间[2..R]的最大值,明显Max[1]>=Max[2].)
所以,实际上,从某下标L开始,Max[L..R]都小于A[R],于是变成了线段树的区间修改:将[L..R]的数变成A[R]。
那么,怎么找到L值呢?在线段树的节点上多维护一个最大值的最大值,然后就可以判断了,具体看代码。
最小值同理。
然后来谈一谈线段树每个节点需要的变量,以下用m表示最小值,用M表示最大值,用L表示长度。
变量分为:标记量和统计量。
先来谈标记量,需要最小值标记,最大值标记,和长度标记,记做m,M,L。
毕竟是区间修改,所以需要三种标记。
统计量:
然后明显要有(最大值*最小值*长度)的和,记做 smML。