题目描述
One day, Snuke was given a permutation of length N, a1,a2,…,aN, from his friend.
Find the following:
Constraints
1≤N≤200,000
(a1,a2,…,aN) is a permutation of (1,2,…,N).
输入
The input is given from Standard Input in the following format:
N
a1 a2 … aN
输出
Print the answer.
Note that the answer may not fit into a 32-bit integer.
样例输入
3
2 1 3
样例输出
9
题意:给出一个序列,对于这个序列的所有L到R上区间取最小值,然后求和。序列中每个数唯一,并且在1到n之间。
对于这样的要求,我们想最小值求和,那么先要找到区间,然后对区间找最小值再求和,那么一个最小值可能是几个区间的最小值,所以我们可能会重复加某个数好几遍。因此反过来想,对于一个数,可能在那些区间中是最小值,可能被加和几次。
在序列中,从1到最小值的位置t,将有t-1+1种包含最小值的区间,在最小值位置t到n这段距离中,存在n-t+1种包含最小值区间的组合。那么所有包含最小值的区间,即
最小值左半边的区间种类*最小值右半边的区间种类
比如2 1 3中,2 1 这段左半边有2种区间种类,1 3这段右半边有2种区间种类,通过相互组合,2*2,发现包含1的区间共有4种。那么这4个区间的最小值都是1,则1被加和了4次。
扩展下去,对于1到n内的最小值是这样计算,那么我们找完了这个范围的最小值,再找不包含整个序列中最小值的两段序列,即 最小值左半边的序列,和最小值右半边的序列,分别在这两个序列中继续找最小值,继续重复计算新的最小值所被包含和计算的区间数量。
即可得到公式:(t-L+1)*(R-t+1)
利用上面公式求得每个最小值再一定区间内被计算的次数,这样不断二分成两段区间,不断计算所有值被计算的次数。最后求和即可得到所有区间最小值的总和,注意有爆int的数据。
用线段树或者RMQ实现区间最小值查询,因为每个数都是唯一的,因此我们将返回的最小值映射到另一个表示该值位序的数组中,方便每次查询最小值之后可以继续找到搜索的新区间边界。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
struct node
{
int l,r,minn;
} tree[200005*4];
int a[200004],n,mp[200005];
LL ans;
void build(int l,int r,int rt)
{
tree[rt].l=l;
tree[rt].r=r;
tree[rt].minn=0x7fffffff;
if(l==r) tree[rt].minn=a[l];
else
{
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
tree[rt].minn=min(tree[rt<<1].minn,tree[rt<<1|1].minn);
}
}
int query(int l,int r,int rt)
{
if(l<=tree[rt].l&&tree[rt].r<=r) return tree[rt].minn;
else
{
int mid=(tree[rt].l+tree[rt].r)>>1;
int minn=0x7fffffff;
if(l<=mid) minn=min(query(l,r,rt<<1),minn);
if(r>=mid+1) minn=min(query(l,r,rt<<1|1),minn);
return minn;
}
}
void dfs(int l,int r)
{
if(r<l) return;
int mini=mp[query(l,r,1)];
// printf("%d===%d==%d
",l,mini,r);
ans+=(LL)(mini-l+1)*(LL)(r-mini+1)*(LL)a[mini];
dfs(l,mini-1);
dfs(mini+1,r);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
ans=0;
for(int i=1; i<=n; i++) scanf("%d",&a[i]),mp[a[i]]=i;
build(1,n,1);
dfs(1,n);
printf("%lld
",ans);
}
}