裏紙

ほぼ競プロ、たまに日記

CF 631 E - Product Sum

問題

Problem - E - Codeforces

問題概要

長さnの1-indexedな配列aがある。この数列に対して一度だけ、数列のある要素を選び、その要素を数列内の好きな位置に移動するという操作をすることができる。

このとき、 c = \sum_{i=1}^n i \cdot a_iの取りうる最大値を求めよ。

  •  2 \le n \le 200000
  •  | a_i | \le 1000000

イデア

何も動かさなかったときのcの値をc'として、動かしたときの差分\Delta cについて考える。 以下、2つの場合について考える。

ある要素を右に動かす場合

l番目の要素を、r番目の要素となるように動かすとする(l \lt r)。 このとき、操作前と操作後でindexはこのようになる。

f:id:imulan:20190503233303p:plain

よって、index1からiまでのaの和をsum_iとすると、

 \Delta c  = l \cdot a_{l+1} + (l+1) \cdot a_{l+2}  + \ldots + (r-1) \cdot a_r + r \cdot a_l  - (l \cdot a_l + (l+1) \cdot a_{l+1} + \ldots + r \cdot a_r )

 =  (r-l) \cdot a_l - a{l+1} - a_{l+2} - \ldots - a_r

 = (r-l) \cdot a_l - (sum_r - sum_l)

 =  r \cdot a_l - sum_r + sum_l - l \cdot a_l

となり、これはCHTを利用し、lを減らしていきながら探索することでO(nlogn)で探索することができる(傾きには単調性がある方向で追加している)。

ある要素を左に動かす場合

r番目の要素を、l番目の要素となるように動かすとする(l \lt r)。

これも同様に計算すると、\Delta c = l \cdot a_r - sum_{l-1} + sum_{r-1} - r \cdot a_rとなり、今度はrを基準にすることで同様に探索ができる。

実装(C++)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i,n) for(int (i)=0;(i)<(int)(n);++(i))
#define all(x) (x).begin(),(x).end()
#define pb push_back
#define fi first
#define se second
#define dbg(x) cout<<#x" = "<<((x))<<endl
template<class T,class U> ostream& operator<<(ostream& o, const pair<T,U> &p){o<<"("<<p.fi<<","<<p.se<<")";return o;}
template<class T> ostream& operator<<(ostream& o, const vector<T> &v){o<<"[";for(T t:v){o<<t<<",";}o<<"]";return o;}

using P = pair<ll,ll>;
struct CHTrick{
    deque<P> lines;
    bool isMonoticX = false;
    CHTrick(bool flag=false){
        isMonoticX = flag;
    }
    // check whether line l2 is useless
    // maybe overflow
    bool check(P l1, P l2, P l3){
        return (long double)(l3.second-l2.second)*(l2.first-l1.first) >= (long double)(l2.second-l1.second)*(l3.first-l2.first);
    }
    // add line(y=ax+b)
    void add(ll a, ll b){
        if(lines.size()>0 && lines.back().first==a){
            // for maximum val, replace min to max
            b = max(b, lines.back().second);
            lines.pop_back();
        }
        P line(a,b);
        while(lines.size()>=2 && check(*(lines.end()-2),lines.back(),line)){
            lines.pop_back();
        }
        lines.push_back(line);
    }
    // return the value of f_i(x)
    ll f(int i, ll x){
        return lines[i].first*x + lines[i].second;
    }
    ll f(P line, ll x){
        return line.first*x + line.second;
    }
    // minimum -> (lv>=rv), maximum ->(lv<=rv)
    bool comp(ll lv, ll rv){
        return lv <= rv;
    }
    // return the minimum(maximum) values among lines
    ll get(ll x){
        if(isMonoticX){
            while(lines.size()>=2 && comp(f(0,x),f(1,x))){
                lines.pop_front();
            }
            return f(0,x);
        }else{
            int low = -1, high = lines.size()-1;
            while(high-low>1){
                int mid=(high+low)/2;
                if(comp(f(mid,x),f(mid+1,x))) low = mid;
                else high = mid;
            }
            return f(high,x);
        }
    }
};

int main(){
    int n;
    scanf(" %d", &n);
    vector<ll> a(n+1);
    rep(i,n) scanf(" %lld", &a[i+1]);

    vector<ll> sum(a);
    rep(i,n) sum[i+1] += sum[i];

    ll c_def = 0;
    for(ll i=1; i<=n; ++i) c_def += a[i]*i;

    ll mx = 0;

    // 追加する直線の傾きを増加方向にするために、傾きだけ-1倍する
    CHTrick mvL;
    mvL.add(-n, -sum[n]);
    for(ll i=n-1; i>0; --i){
        // 投げるクエリも-1倍
        ll v = mvL.get(-a[i]) + sum[i] - a[i]*i;
        mx = max(mx, v);
        mvL.add(-i, -sum[i]);
    }

    CHTrick mvR;
    mvR.add(1, -sum[0]);
    for(ll i=2; i<=n; ++i){
        ll v = mvR.get(a[i]) + sum[i-1] - a[i]*i;
        mx = max(mx, v);
        mvR.add(i, -sum[i-1]);
    }

    printf("%lld\n", c_def + mx);
    return 0;
}