【AtCoder】AGC006 Minimum Sum

题目

传送门

题目大意

给出一个 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=1Nr=lNmin{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=1Nai×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 &lt; a i a_l&lt;a_i al<ai
  • 找到 a i a_i ai右边离它最近 r r r,满足 a r &lt; a i a_r&lt;a_i ar<ai
  • f ( i ) = ( i − l ) × ( r − i ) f(i)=(i-l)\times (r-i) f(i)=(il)×(ri)

这样找到的区间 ( l , r ) (l,r) (l,r)(注意是开的)是满足最小值为 a i a_i ai的最大的一个区间。因为区间 ( l − 1 , r ) (l-1,r) (l1,r)的最小值就不是 a i a_i ai了,是 a l a_l al a l &lt; a i a_l&lt;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)=(il)×(ri)

于是你发现,暴力完成这个结论还是 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值