予鈴

アウトプットとメモ書きの中間みたいな記事がたくさん出ます。

C - Mandarin Orange

問題

 N個の数列が与えられる.  \sum_{i = l}^r min(A_l, A_{l + 1}, ..., A_r)を求めよ.

atcoder.jp

解法

問題文から上記の言い換えは比較的容易.後はこれを埋めて行けば良い.
これが茶diffと聞いてインフレもここまで来たか…と思ったが、実際は O(N^2)通るらしい.

これでは面白く無いので、 O(N log N)解法を考えてみよう.

区間の両端を決めるのは難しい. 区間の片方を固定する あるいは、数列の値が最小値となる区間を導出するといった方法が考えられる.

今回は後者を使う.


さて、ある値 v_kが最小値となる区間を求めようと思うと、 l < k \land v_l < v_k となる l を探し出せば良い.rも同様なので割愛する.
これを求めるには、 Aをソートしておき、先頭から値を順番に舐めて、その添字の情報を保存しておけば良い.なぜなら、 v_kが最小値となる区間は、 v_kより小さい値を持つ要素の添字から導出できる.探索済みの配列の添字を高速に探索には、std::setなどの二分探索を用いれば良い.
計算量は O(N log N).

提出

#include <bits/stdc++.h>

using namespace std;

using Int  = long long;

int main()
{
    Int N; cin >> N;
    vector<pair<Int,Int>>v;
    
    set<Int>rIdx,lIdx;
    
    Int ans = 0;
    
    for(Int i = 0; i < N; ++i)
    {
        Int t; cin >> t;
        v.emplace_back(t,i);
    }
    sort(v.begin(),v.end());
    
    for(int i = 0; i < N; ++i)
    {
        const auto [val,idx] = v[i];
        
        auto itr = rIdx.upper_bound(idx);
        auto it2 = lIdx.upper_bound(-idx);
        
        Int r = -1, l = -1;
        
        if(itr == rIdx.end())
            r = N;
        else
            r = *itr;
        
        if( it2 == lIdx.end())
            l = 0;
        else
            l = abs(*it2) + 1;
        
        ans = max(ans, (r - l) * val);
        
        rIdx.emplace(idx);
        lIdx.emplace(-idx);
    }
    
    cout << ans << endl;
}