AtCoder Regular Contest 092 E - Both Sides Merger

「3月中に毎日1問解いて黄色になろう!」キャンペーン7問目。3日に1問ペースすら崩れてると思いきや、残念ながら2問連続投稿でペース通りです。

問題概要

以下の操作を与えられた配列に残り要素が1つになるまで行う。最大値とそれを得る方法を求めよ。

  1. 配列の要素を1つ選ぶ
  2. 選んだ要素が配列の端なら削除
  3. そうでなければその両隣の要素の和にその要素を書き換え、両隣を削除

E - Both Sides Merger

解説

実験していると、割とすぐに「偶奇で分けて、その中では任意に選ぶことが出来る」ということが分かります。具体的には、

A=[1,3,5,2,4]

としたときに、1と4を組み合わせるなら3番目で[1,5,4]にする→[5(1+4)]にするという感じで、奇数個離れていれば自由に組み合わせられます。逆に、偶数個離れているときはどう頑張っても無理です。というのも、3の操作で間の間隔が2減るので。

「偶奇で分けた後、"""自由に"""組み合わせることが出来る」ので、偶奇別にmax(0,a[i])の総和が大きいほうを採用します。作り方も答えないといけなくて、これは右端から消していく感じで頑張って実装すると出来ます。O(N)で制約が非常に緩いので、まあ出来るかと。偶数の時は最初にa[0]を削除すると奇数のパターンに容易に帰着できます。

ここで気を付けなくてはいけないのが、「全部負の数」のパターン。max(0,a[i])で全部ダメになるので、これだけ例外処理をしなくてはなりません。この時は足し算すると損をするので、1要素以外すべて2の方法で消します。これは割と簡単に実装できるのではないかと。右から消していくと、要素を過ぎた後が2ではなく3の処理になってしまうので、「1」を連発するか、左から消すことをお勧めします。

https://beta.atcoder.jp/contests/arc092/submissions/2240774

int main() {
    int n; cin >> n;
    vll a(n);
    rep(i, n)   cin >> a[i];
    bool minus = true;
    rep(i, n)   if (a[i] > 0LL)   minus = false;
    if (minus) {
        // 一番大きい要素以外全削除
        int x = 0;
        rep(i, n)   if (a[x] < a[i])   x = i;
        cout << a[x] << endl;
        cout << n - 1 << endl;
        for (int i = n; i > x + 1; i--)  cout << i << endl;
        for (int i = x; i > 0; i--)  cout << 1 << endl;
        return 0;
    }
    ll ret1 = 0, ret2 = 0;
    for (int i = 0; i < n; i += 2)  ret1 += max(0LL, a[i]);
    for (int i = 1; i < n; i += 2)  ret2 += max(0LL, a[i]);
    vint ret;
    if (ret1 < ret2) {
        ret1 = ret2;
        ret.push_back(1);
        n--;
        a.erase(a.begin());
    }
    if (n % 2 == 0) {
        ret.push_back(n);
        n--;
    }
    while (a[n - 1] <= 0) {
        ret.push_back(n);
        n--;
        ret.push_back(n);
        n--;
    }
    while (n > 1) {
        while (n >= 5 && a[n - 3] <= 0) {
            ret.push_back(n - 2);
            n -= 2;
        }
        if (n == 1)  break;
        else if (n == 3) {
            if (a[0] >= 0) {
                ret.push_back(2);
                n = 1;
                break;
            }
            else {
                ret.push_back(1);
                ret.push_back(1);
                n = 1;
                break;
            }
        }
        else {
            ret.push_back(n - 1);
            n -= 2;
        }
    }
    cout << ret1 << endl;
    cout << ret.size() << endl;
    rep(i, ret.size())  cout << ret[i] << endl;
    return 0;
}

感想

Dより簡単なのは思考できたからなんでしょうね…思いつかなきゃ思いつかないです。精進しましょう。