競プロやります。

競プロを頑張るブログです。

diverta 2019 Programming Contest 2

C - Successive Subtraction

https://atcoder.jp/contests/diverta2019-2/tasks/diverta2019_2_c

方針

index は 0 始まりとします。 A はソートしてよいので、以下では  A_0 \leq A_1 \leq \dots \leq A_{N-1} とします。また、最後に残る値を  M とします。

 M は各  A_i の符合を適当に変えたのちに総和を取ったものだとみなすことが出来ます。
一番都合のよいことを考えると、 A_i < 0 なる  i については全て符号を変え、 A_i > 0 なる  i については全てそのままにして  M としたいですが、それは無理かもしれません。もし  k 個の  i を選んで符号を変えられるのだとすると、 A_0, \dots A_{k-1} の符合を変えるべき、ということになります。

実は  k = 1, 2, \dots , N-1 が許される値となります。これは  N についての数学的帰納法で示すことが出来ます。
まず  N = 2 のときは自明です。
次に  N = n の場合にも成立することを仮定すると、 N = n+1 の場合に  n 回の操作をした時点で、符号を変えた値は  1,2, \dots, n-1 個のどれかになります。このとき、これまで一度も操作の対象とせずに残ってきた  A_i y として選び、もう一方の値を  x とすることで、 k = 2,3,\dots, n が達成できます。逆に、 x = A_i とすることで、 k = 1,2,\dots, n-1 が達成できるので、示されました。


以上により  k = 1,2,\dots, N-1 がわかりましたが、ではどの  k のときに  M が最大になるのかを考えたいです。
これは  A_i の累積和  S_i = \sum_{n=0}^{i} A_n を前計算しておくと、 k 個の符合を変えるときの  M M = S_{N-1} - 2S_{k-1} O(1) で計算できるので、全ての  k について  M を求めてその最大値をとればよいです。このときの  k k=K とします。

 A_0, \dots , A_{K-1} の符合を変え、残りはそのままにするような操作の仕方は以下のようになります。

  1.  x = A_{N-1} y = A_i~(i = 1, 2, \dots, K-1) として操作を行う。この時点で  A_{N-1} A_{N-1}^{\prime} = A_{N-1} - A_1 - A_2 - \dots - A_{K-1} となる。
  2.  x = A_0 y = A_i~(i=K, K+1,\dots N-2) として操作を行う。この時点で  A_0 A_{0}^{\prime} = A_0 - A_{K} - A_{K+1} - \dots - A_{N-2} となる。
  3.  x = A_{N-1}^{\prime} y = A_0^{\prime} とすると、 M = (A_{N-1} + A_{N-2} + \dots + A_{K}) - (A_{K-1} + A_{K-2} + \dots + A_{0}) が得られる。

ソースコード

#include <bits/stdc++.h>
using namespace std;

int main(){
    int N;  cin >> N;
    vector<int> A(N,0);
    for(int i=0; i<N; i++) cin >> A[i];
    sort(A.begin(), A.end()); // A はソートしておく

    vector<int> S(N,0); // A の累積和
    S[0] = A[0];
    for(int i=1; i<N; i++){
        S[i] = S[i-1] + A[i];
    }
    
    int M = -1e9;  // 最後に残る値の最大値
    int K = 0;     // M が得られるときに符号を変えた A[i] の数 
    for(int k=1; k<=N-1; k++){
        // k 個の A[i] の符合を反転させる場合は
        // M = S[N-1]-S[k-1]*2 となる。 
        if(M < S[N-1]-S[k-1]*2){
            M = S[N-1]-S[k-1]*2;
            K = k;
        }
    }

    cout << M << endl;
    // 操作の 1.
    for(int i=1; i<K; i++){
        cout << A[N-1] << " " << A[i] << endl;
        A[N-1] = A[N-1] - A[i];
    }
    // 操作の 2.
    for(int i=K; i<N-1; i++){
        cout << A[0] << " " << A[i] << endl;
        A[0] = A[0] - A[i];
    }
    // 操作の 3.
    cout << A[N-1] << " " << A[0] << endl;
}