1. 程式人生 > >【AtCoder】AGC006 Minimum Sum

【AtCoder】AGC006 Minimum Sum

文章目錄

題目

傳送門

題目大意

給出一個NN的排列a1,a2,...,aNa_1,a_2,...,a_N,求l=1Nr=lNmin{al,al+1,...,ar}\sum\limits_{l=1}^{N}\sum\limits_{r=l}^{N}\min\{a_l,a_{l+1},...,a_r\}

思路

題意就是求序列中每個區間的最小值之和。 考慮有多少個區間的最小值為aia_i,記為f(i)f(i),則答案為:i=1Nai×f(i)\sum\limits_{i=1}^{N}a_i\times f(i)

若區間[l,r][l,r]的最小值為aia_i,則其中的每一個數(除了aia_i)都比aia_i大。 直接說結論:

  • 找到aia_i左邊離它最近ll,滿足al<aia_l<a_i
  • 找到aia_i右邊離它最近rr,滿足ar<aia_r<a_i
  • f(i)=(il)×(ri)f(i)=(i-l)\times (r-i)

這樣找到的區間(l,r)(l,r)(注意是開的)是滿足最小值為a

ia_i的最大的一個區間。因為區間(l1,r)(l-1,r)的最小值就不是aia_i了,是ala_lal<aia_l<a_i),同理,區間(l,r+1)(l,r+1)的最小值一定是ara_r。 但是在區間(l,i)(l,i)(i,r)(i,r)中的每個數都比aia_i大,從(l,i](l,i]中選出一個作為左端點,[i,r)[i,r)中選出一個作為右端點,得到f(i)=(il)×(ri)f(i)=(i-l)\times (r-i)

於是你發現,暴力完成這個結論還是O(N2)O(N^2)的……

如果我們將比aia_i小的數的下標存在一個set<int> S裡面,那麼l=S.lower_bound(i)(實現的時候用lower_bound好像會有神奇之事發生,詳見程式碼),r=S.upper_bound(i)。 所以將aa排個序(和下標一塊,用結構體),然後順著掃,將aia_i前面的數都扔到set裡面,再對aia_i的下標(排了序就不是ii了)找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);
}