考虑一次查询,比如查询 [l,r] 区间。
首先找出区间内最小值的位置,设为 pos。
考虑把询问的区间的子区间分成三种:
- 跨过了 pos
- 左右端点都在 [l,pos−1]
- 左右端点都在 [pos+1,r]
考虑分别处理。对于第一种,明显贡献是 (pos−l+1)(r−pos+1) 。
下面设 [l,r][L,R] 表示左端点在 [l,r] 内,右端点在 [L,R] 内的所有区间的最小值的和。
我们考虑怎么求 [l,r][l,r] 的值。我们把这个拆成:
[l,n][l,n]−[r+1,n][r+1,n]−[l,r][r+1,n]也就是左右端点都在 [l,n] 减去左右都在 [r+1,n] 再减去左端点在 [l,r] 右端点在 [r+1,n] 的贡献。
怎么求 [l,n][l,n] 呢,考虑预处理出来。为了方便设 f(i)=[i,n][i,n] ,于是可以发现
f(i)=f(i+1)+[i,i][i,n]再设 F(i)=[i,i][i,n] 。考虑怎么递推出 F 。对于每个位置 i ,设 ri 为满足 ari<ai 的最小值,也就是说 ai 小于 [i,ri−1] 的所有值。于是
Fi=Fri+ai(ri−i)于是递推求出了 F 也就求出了 f 。
回到原来的问题,我们还需要求 [l,r][r+1,n]。这个东西看起来求不了。但是,对于第二种询问,我们要求的是
[l,pos−1][pos,n]由于 apos 是 [l,r] 的最小值,这个东西直接等于 (pos−l)Fpos。因为左端点的选择是 [l,pos−1] 中任意选择,且每个区间都一定是 pos 后面更小,所以就是 Fpos 的 pos−l 倍了。
[pos+1,r] 呢?倒过来(后缀变前缀)一样整一遍即可。
实现上,求 li,ri 直接单调栈即可。复杂度瓶颈在 ST 表的 O(nlogn)−O(1) ,用转01 RMQ 后整 线性的 ST 表也可以做到 O(n)−O(1)。
代码很好写+好调。。代码后面留了个 gen。
cpp
1 |
|