题目
题目大意
给出一个 N N N的排列 a 1 , a 2 , . . . , a N a_1,a_2,...,a_N a1,a2,...,aN,求 ∑ l = 1 N ∑ r = l N min { a l , a l + 1 , . . . , a r } \sum\limits_{l=1}^{N}\sum\limits_{r=l}^{N}\min\{a_l,a_{l+1},...,a_r\} l=1∑Nr=l∑Nmin{al,al+1,...,ar}。
思路
题意就是求序列中每个区间的最小值之和。
考虑有多少个区间的最小值为
a
i
a_i
ai,记为
f
(
i
)
f(i)
f(i),则答案为:
∑
i
=
1
N
a
i
×
f
(
i
)
\sum\limits_{i=1}^{N}a_i\times f(i)
i=1∑Nai×f(i)
若区间
[
l
,
r
]
[l,r]
[l,r]的最小值为
a
i
a_i
ai,则其中的每一个数(除了
a
i
a_i
ai)都比
a
i
a_i
ai大。
直接说结论:
- 找到 a i a_i ai左边离它最近的 l l l,满足 a l < a i a_l<a_i al<ai
- 找到 a i a_i ai右边离它最近的 r r r,满足 a r < a i a_r<a_i ar<ai
- f ( i ) = ( i − l ) × ( r − i ) f(i)=(i-l)\times (r-i) f(i)=(i−l)×(r−i)
这样找到的区间
(
l
,
r
)
(l,r)
(l,r)(注意是开的)是满足最小值为
a
i
a_i
ai的最大的一个区间。因为区间
(
l
−
1
,
r
)
(l-1,r)
(l−1,r)的最小值就不是
a
i
a_i
ai了,是
a
l
a_l
al(
a
l
<
a
i
a_l<a_i
al<ai),同理,区间
(
l
,
r
+
1
)
(l,r+1)
(l,r+1)的最小值一定是
a
r
a_r
ar。
但是在区间
(
l
,
i
)
(l,i)
(l,i)和
(
i
,
r
)
(i,r)
(i,r)中的每个数都比
a
i
a_i
ai大,从
(
l
,
i
]
(l,i]
(l,i]中选出一个作为左端点,
[
i
,
r
)
[i,r)
[i,r)中选出一个作为右端点,得到
f
(
i
)
=
(
i
−
l
)
×
(
r
−
i
)
f(i)=(i-l)\times (r-i)
f(i)=(i−l)×(r−i)。
于是你发现,暴力完成这个结论还是 O ( N 2 ) O(N^2) O(N2)的……
如果我们将比
a
i
a_i
ai小的数的下标存在一个set<int> S
里面,那么l=S.lower_bound(i)
(实现的时候用lower_bound
好像会有神奇之事发生,详见代码),r=S.upper_bound(i)
。
所以将
a
a
a排个序(和下标一块,用结构体),然后顺着扫,将
a
i
a_i
ai前面的数都扔到set
里面,再对
a
i
a_i
ai的下标(排了序就不是
i
i
i了)找lower_bound
之类就可以了。
似乎有点像偏序……
反正这个问题我想了几百年……
代码
#include<set>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 200000
int N;
struct node{
int val,ID;
}A[MAXN+5];
bool cmp(node x,node y){
return x.val<y.val;
}
int main(){
scanf("%d",&N);
for(int i=1;i<=N;i++){
A[i].ID=i;
scanf("%d",&A[i].val);
}
sort(A+1,A+N+1,cmp);
set<int> index;
index.insert(0),index.insert(N+1);//免得出现找不到的情况
long long Ans=0;
for(int i=1;i<=N;i++){
set<int>::iterator Left,Right;
Left=Right=index.upper_bound(A[i].ID),Left--;//注意Left的处理
Ans+=1ll*(A[i].ID-*Left)*(*Right-A[i].ID)*A[i].val;
index.insert(A[i].ID);
}
printf("%lld",Ans);
}