むかでのチラ裏

競技プログラミング現役引退でチラ裏に・・・

AtCoder Beginner Contest 176 F - Brave CHAIN

精進の記録をどのように管理していくか迷っていたんですが,ブログのエントリとして保管することにしました.

本記事ではABC176 F - Brave CHAINを扱います.ちょっと特殊なDPの問題でした.

atcoder.jp

問題概要

$ N $ 以下の自然数が書かれた $ 3N $ 枚カードが一列に並んでいる.これに対して以下の操作を $ N-1 $ 回行う.

  • 左から $ 5 $ 枚のカードを好きに並び替えて左から $ 3 $ 枚を取り除き,この $ 3 $ 枚が同じ数字ならば $ 1 $ 点得る. 最後に余った $ 3 $ 枚のカードが同じ数字なら追加で $ 1 $ 点得る.

制約

  • $ 1 \leq N \leq 2000 $
  • $ 1 \leq A_i \leq N $

考察

問題文のままの通り操作を考えると何をすればいいのかわからないのでまず操作を分析する.

操作には「並び替える」と書いてあるが,取り除く $ 3 $ 枚のカードは同じ数字であるかのみが重要なので順不同である.また,残す $ 2 $ 枚のカードについても次の操作で好きに並び変えられるため順不同である.

以上のことから $ 1 $ 回の操作は

  • 左 $ 5 $ 枚のうち残す $ 2 $ 枚を選んで残り $ 3 $ 枚が同じ数字なら $ 1 $ 点得る

というように言い換えられる.これに関しては直感的にすぐ言い換えられる人が多そう.

操作をこのように言い換えることができると,$ i (\geq 2)$ 回目の操作では $ i-1 $ 回目の操作で残した $ 2 $ 枚のカードと,新しく操作範囲に入る $ 3 $ 枚で操作を行うことになる.この「新しい」 $ 3 $ 枚は $ i $ のみに依存するため,方針としては残した $ 2 $ 枚をなんらかの方法で管理するのが良いことがわかってくる.最初の2枚を $ 0 $ 回目の操作で残したカードと考えることで $ i = 1 $ でも上記のことが言えるようになる.

f:id:spider_0859:20200829131540p:plain

今までの考察から上図のように列を分割したくなる (入力例 $ 2 $ ).

何も考えずに全探索をすると到底間に合わないことは明らかで,探索空間を絞ったり貪欲に選ぶことも難しそうなのでDPを考える (制約が微妙に小さいところがそれっぽく感じました).上でも述べたように残した $ 2 $ 枚の情報のみを管理すればいいのでDPは次のように考えられる.

  • $ dp[i][j][k] $ : $ i $ 回目の操作までして残った数の組が $ (j, k) $ である場合の得点の最大値

このDPの遷移は $ 5 $ 枚から $ 2 $ 枚選んで残り $ 3 $ 枚が同じが調べるだけなので $ O(1) $ になり,状態数が $ O(N^{3}) $ のため,全体の計算量は $ O(N^{3}) $ になる.しかし $ O(N^{3}) $ の解はこの問題では通らないのでさらに考察を進める.

残した $ 2 $ 枚の情報は最低限必要なので状態数を減らすことは難しい.ここでDPの更新回数を考えてみる.前回の操作で残したカードをそのまま次も残す場合は基本的に更新は行われないが,唯一残りの $ 3 $ 枚のカード (上の図の三つ組) が同じ数字だった場合のみ更新が行われる.しかし,実は三つ組の数字がすべて等しいときはその $ 3 $ 枚を取り除くのが最善手であることが証明できる.

▶証明 (というより正当性の確認)

以上のことから,$ 3 $ つの数字がすべて等しい組は取り除いて最後に答えに $ +1 $ すればよいことがわかる.

三つ組をそのまま取り除く場合の更新については考慮できたので,後は三つ組の数字を少なくとも $ 1 $ つは残す場合の更新を考えれば良い.三つ組を数字を $ (A,B,C) $ とする.$ 3 $ 枚のカードのうちどれかを残す場合,残すカードの組は $ (A,x), (B,x), (C,x) $ ($ x \in [1:N] $) のいずれかになる.よって遷移先の状態 (更新箇所) の数は $ O(N) $ となる.

また,$ dp[j][k] $ のように $ i $ を省略することで三つ組をそのまま取り除く場合の更新を考える必要がなくなる ( $ dp[i+1][j][k]=max(dp[i+1][j][k], dp[i][j][k]) $ の更新が不要になる ).よって三つ組 $ 1 $ つに対する更新箇所の数は $ O(N) $ となる.

次に $ 1 $ 箇所を更新する計算量について考える.

  1. 新たな $ 3 $ 枚のカードのうち $ 1 $ 枚を残す場合. $ (x \notin [A,B,C], y \in [A,B,C] ) $ の状態に遷移することになる.$ (x, i \in [1:N]) $ から遷移することになる.つまり,$ x $ を含む状態のうち $ dp $ の最大値で更新すれば良く,これは $ dp 2[x] $ として $ dp $ の更新時に一緒に更新することで $ O(1) $ で取得することができる.また,$ 3 $ 枚のうち残された $ 2 $ 枚のカードが同じ数字 ( $ \alpha $ とする ) の場合,$ (x, \alpha) $ の状態から遷移すると取り除く $ 3 $ 枚のカードは同じであるので $ dp[x][\alpha]+1 $ の値で更新すれば良い.

  2. 新たな $ 3 $ 枚のカードのうち $ 2 $ 枚を残す場合. $ (x \in [A,B,C], y \in [A,B,C]) $ の状態に遷移することになる.この場合,あらゆる状態から遷移することになり,$ \bf { 1. } $ の場合と同様に $ dp $ 全体の最大値を管理して,この値で更新すれば良い.また,$ 3 $ 枚のうち残された $ 1 $ 枚を $ \alpha $ とする.$ (\alpha, \alpha) $ の状態から遷移するとき取り除く $ 3 $ 枚のカードは同じであるので $ dp[\alpha][\alpha]+1 $ の値で更新すれば良い.

以上より, $ 1 $ 箇所を更新する計算量は $ O(1) $ である.三つ組 $ 1 $ 組に対する更新の計算量を $ O(N^{2}) $ から $ O(N) $ に落とすことができた.これにより全体の計算量は $ O(N^{2}) $ となり今回の制約でも十分間に合うようになる.

個人的なポイント

  • 操作が扱いにくい場合は操作の特徴を分析して言い換えをする.
  • DPの更新に必要な情報と不要な情報をきちんと分ける.(今回は残る2枚の情報のみが必要だった)
  • DPの状態数をそれ以上削減できないような場合でも毎ループの更新箇所が少ない場合は計算量が落ちる時もある.
    • 今回は状態が変化しない場合のDPの更新が不要 + それ以外の場合の更新箇所が少なかったためオーダの $ N $ が一つ落ちた.
  • DP配列を毎ループ使いまわして更新の順序が不明の場合,更新クエリをqueueに入れておいて後でまとめて更新すると良さそう.
  • 組を状態として持つとき,順序付きで管理するがいちいちswapするのは面倒なのでDPテーブルの更新を関数化すると嬉しい.

ソースコード

提出URL

▶ソースコードを展開