実装が少し大変だった。そして、両端から Greedy で追い詰めていけばよいのは思いつかなかった。チームメイトが思いついていた。
問題概要
1 から までの整数が書かれたカードが合計で
枚あり、整数
の書かれたカードは
枚ある。各
に対して、整数
の書かれたカードを追加して以下の条件を満たせるかを判定せよ。
【条件】
「同じ数字の書かれた 3 枚のカード」または「連続した数字の書かれた 3 枚のカード」を選び取り除くことを 回繰り返して、持っているカードを空にすることができる。
制約
考えたこと
まず、 枚のカードに対して、条件を満たせるかを判定する方法から考えてみよう。
もし、1 の書かれたカードに対して、「1, 2, 3」という操作を 3 回行ったとしたとき、それは「1, 1, 1」「2, 2, 2」「3, 3, 3」という操作と等価であるから、操作「1, 2, 3」は高々 2 回までとしてよい。言い換えれば、 であるならば、
になるまで操作「1, 1, 1」を繰り返せば良い。
の状態にしたあと、操作「1, 2, 3」を
回行う必要がある。このとき、
または
ならば、その時点で条件を満たさないと判定できる。
かつ
であるならば、
から
を引く。
その後は、2 の書かれたカードに対して、先ほどと同様の考察をすればよい。これで、 枚のカードに対して条件を満たせるかどうかを判定する方法がわかった。
左右両端から
その後、チームメイトのアトリウム君が「右側からも同じ Greedy ができるから、左右両端から Greedy していって、最後の 5 枚をアドホックに見ればいいじゃん」と言った。それでいいじゃんとなって、アトリウム君が実装して AC となった。
僕自身も復習のために実装することにした。整数 の書かれたカードを加えて条件を満たすかどうかは、次の方法によって判定することができる。
- 整数
の書かれたカードをすべて 0 枚にする(左から Greedy に基づく操作を施して、整数
の書かれたカードもそれに合わせた枚数を除去する)
- 整数
の書かれたカードをすべて 0 枚にする(右から Greedy に基づく操作を施して、整数
の書かれたカードもそれに合わせた枚数を除去する)
- その後、整数
の書かれたカードについて、3 枚ずつ作れるかを判定する
よって、次の値を前処理で求めておくことにした。
left[l]
:先述の Greedy 解法に基づいて数字の書かれたカードをすべて 0 枚にするように操作したとき、数字
の書かれたカードを除去する枚数と、数字
の書かれたカードを除去する枚数
right[r]
:先述の Greedy 解法に基づいて数字の書かれたカードをすべて 0 枚にするように操作したとき、数字
の書かれたカードを除去する枚数と、数字
の書かれたカードを除去する枚数
これらを の計算量であらかじめ求めておくことで、各
について条件を満たすかどうかを
の計算量で判定できる。
全体として の計算量の解法が得られた。
コード
#include <bits/stdc++.h> using namespace std; using pint = pair<int, int>; int main() { // res[l] := A を左からの Greedy で値 [0, l) を全消しするとき、値 l+1, l+2 を何個消す必要があるか auto calc = [&](vector<int> A) -> vector<pint> { vector<pint> res({pint(0, 0)}); for (int i = 0; i < A.size(); i++) { auto [a, b] = res.back(); // 区間 [0, i) ができないとき、区間 [0, i+1) もできない if (a == -1) { res.emplace_back(-1, -1); continue; } if (A[i] < a) { res.emplace_back(-1, -1); continue; } A[i] -= a, A[i] %= 3; res.emplace_back(b + A[i], A[i]); } return res; }; int N, M; cin >> N >> M; vector<int> A(N); for (int i = 0; i < N; i++) cin >> A[i]; // A の左から left 個、右から right 個のみを 0 にしたときの影響を求める auto left = calc(A); reverse(A.begin(), A.end()); auto right = calc(A); reverse(A.begin(), A.end()); // 各 x について条件判定する vector<int> res; for (int x = 0; x < N; x++) { int l = max(x-2, 0), r = min(x+3, N); vector<int> B(r-l, 0); for (int i = l; i < r; i++) B[i-l] = A[i]; B[x-l]++; if (left[l].first == -1 || right[N-r].first == -1) continue; B[0] -= left[l].first, B[1] -= left[l].second; B[r-l-1] -= right[N-r].first, B[r-l-2] -= right[N-r].second; auto tmp = calc(B); if (tmp.back() == pint(0, 0)) res.emplace_back(x); } cout << res.size() << endl; for (auto val : res) cout << val+1 << " "; cout << endl; }