コーナーケースがエグい
問題概要
個の非負整数 が与えられる。これらの並び替え であって
が最大となるものを求めよ。複数通り考えられる場合には辞書順最小のものを答えよ。ただし、 とする。
制約
考えたこと
エグい場合分けが大量に発生しそうなので注意したい。たとえば
- <
- =
- >
- >
- ...
みたいなケースもある。とりあえず例外的なものから除外していこう。
0 と 1 のみを含む場合
まずは 0 と 1 のみを含む場合から解いてみる。0 と 1 の乱舞がとにかくヤバそうだからだ。
0 の個数を 個、 の個数を 個とする。次のように求められる。
- のとき
- が偶数のとき:最終結果は 1 となる
- が奇数のとき:最終結果は 0 となる
- のとき、最終結果は 1 となる ( というプレイができるので、 の個数を疑似的に偶数個にできる)
これらを踏まえて、辞書順最小のものを頑張って求める。 のケースであっても、かならずしも最初に 1 を持ってくる必要はない。
たとえば のとき、次の並びが最適となると思われる。
(0, 0, 0, 0, 0, 0, 1, 0, 1, 1)
つまり、
- が偶数のとき、「0 を最初に全部並べてから 1 を全部並べる」が最適
- が奇数のとき、「0 を 個並べて、(1, 0) と並べて、残りの 1 を全部並べる」が最適
となる。
0 も 1 も含まない場合
今度は、0 も 1 も含まない場合を解いてみる。比較的やりやすそうだ。一般に
となる条件を解析すると、 の極値が であることから、 または が 2 のケースだけちょっと特殊であることがわかる。
- かつ の場合は、 のとき となる
- かつ の場合は、
- のときは、
- のときは、
- のときは、
以上を踏まえて、次のように解ける (0 と 1 は含まないとする)。
- 数列を小さい順に並び替える
- 最後の 2 項が (2, 3) の場合は、そこだけ swap する
一般の場合
以上の考察をマージして、一般の場合を考える。
0 と 1 のみの部分の最適解が 1 のとき
この場合は
(2 以上の並びの最適解)(0, 1 の並びの最適解)
とすれば OK。
0 と 1 のみの部分の最適解が 0 のとき
つまり「1 がなく、0 が奇数個」という場合だ。この場合は、「2 以上の並び」を最適化していても、その末尾が無効化されてしまうことに注意。つまり、
「2 以上の値のうちの 1 個を 1 に変換する操作」
が付け加えられる格好になる。それならば、「2 以上の値のうちの最小値」をその対象とするのが良いだろう (最小以外のものを 1 にすると結果は必ず悪化する)。つまり
(2 以上の値のうち最小のものを除いたものの並びの最適解),
(2 以上の値のうちの最小値),
(0 の並び)
というふうに並べることで最適値が出せることがわかる。しかしそのうちの辞書順最小のものを求めよ、と問われたならばもう少し考える必要がある。たとえば
(4, 4, 5, 7, 3, 0, 0, 0, 0, 0)
という並びに対して、
(4, 4, 5, 7, 0, 0, 0, 0, 3, 0)
としても変わらないことがわかる。(3, 0, 0, 0, 0, 0) も (0, 0, 0, 0, 3, 0) も 1 を導くからだ。つまり、以下のように並べるのが最適となる!
- (2 以上の値のうち最小のものを除いたものの並びの最適解)
- 0 を 個
- 2 以上の値のうちの最小値
- 0
まとめ
場合分けがエグいことになったが、まとめる。0 の個数を 、1 の個数を とする。
- で が奇数のとき
- 2 以上の値のうち最小のものを とする
- 2 以上の値のうち 以外のものについて、小さい順に並べる
- 末尾が (2, 3) ならば swap する
- 0 を 個並べる
- 最後に を付け加える
- それ以外のとき
- 2 以上の値を小さい順に並べる
- 末尾が (2, 3) ならば swap する
- 以下の最大の偶数の分、0 を並べる
- 1 を 1 個並べる (存在しなければ何もしない)
- 残りの 0 を並べる (存在しなければ何もしない)
- 残りの 1 を並べる
計算量は 。
#include <bits/stdc++.h> using namespace std; int main() { int N; cin >> N; int zero = 0, one = 0; deque<int> res; for (int i = 0; i < N; ++i) { int val; cin >> val; if (val == 0) ++zero; else if (val == 1) ++one; else res.push_back(val); } sort(res.begin(), res.end()); auto matsubi = [&](deque<int> &A) { int n = (int)A.size(); if (n >= 2 && A[n-2] == 2 && A[n-1] == 3) swap(A[n-2], A[n-1]); }; if (one == 0 && zero % 2 == 1) { int vmin = res[0]; res.pop_front(); matsubi(res); for (int i = 0; i < zero-1; ++i) res.push_back(0); res.push_back(vmin); res.push_back(0); } else { matsubi(res); for (int i = 0; i < zero/2*2; ++i) res.push_back(0); if (one) res.push_back(1); if (zero % 2 == 1) res.push_back(0); for (int i = 0; i < one-1; ++i) res.push_back(1); } for (auto v : res) cout << v << endl; }