むかでのチラ裏

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

Codeforces Round #689 (Div. 2) E - Water Level

露骨に場合分けをしなくてはいけないような問題で苦手だったので記事にして残しておきます.

codeforces.com

問題概要

最初,ウォータークーラーには $ l $ リットルの水が入っている. $ 1 $ 日に $ x $ リットルの水が消費される.また, $ 1 $ 日の初めに $ y $ リットル補充することができる.ウォータークーラー内の水の量がどの時点でも $ [l, r] $ の範囲内に収まるように $ t $ 日間過ごすことは可能かどうか答えよ.

制約

  • $ 1 \leq l \leq k \leq r \leq 10^{18} $
  • $ 1 \leq t \leq 10^{18} $
  • $ 1 \leq x \leq 10^{6} $
  • $ 1 \leq y \leq 10^{18} $

考察

まず日ごとの操作について考えてみる.

$ 1 $ 日に $ x $ リットルは確実に消費される.一方で $ 1 $ 日の初めに $ y $ リットル供給するかどうかは任意に決めることができる.つまり, $ 1 $ 日の終わりの時点でウォータークーラーに入っている水の量は $ (-x) $ or $ (y-x) $ リットル増加すると言える.

ただし注意しなければならない点として現在の水の量を $ k' $ として $ k'+y \leq r $ を満たす場合のみ $ (y-x) $ 増加を選択することができる( $ r $ リットルを超えてはならないため).この条件を供給条件と呼ぶことにする.

ここで $ (-x) $ は必ず負だが $ (y-x) $ の正負は $ x $ と $ y $ の大小によるので場合分けをする.

$ y-x \leq 0 $ の場合

選択肢の両方が負であるため $ (y-x) $ 増加を選択できる日は選択する方が良いのは明らか.つまり, 供給条件を満たすようになるまで $ (-x) $ 増加を選択したあと水の量が $ L $ 未満にならないギリギリまで $ (y-x) $ を選択するのが最適だとわかる.

供給条件を満たすようになるために選択すべき $ (-x) $ の回数を $ n $ とすると次の式が成り立つ. $$ k-nx+y \leq r $$ $$ \therefore n \geq \left\lceil{\frac{k+y-r}{x}}\right\rceil $$ $ n $ を最小化したいので右辺と等しいとする.

ここで $ k-nx < l $ の場合,つまり供給条件を満たすために必要な回数の $ (-x) $ を実行すると水の量が $ L $ リットルを下回ってしまう場合, 供給条件を満たすこと不可能. $ (-x) $ を実行できる回数を $ m $ とすると $ k-mx \geq l $ より $ m \leq \left\lfloor{\frac{k-l}{x}}\right\rfloor $ となり,この場合は右辺が $ t $ 以上なら"Yes".

それ以外の場合, $ k $ を $ nx $,$ t $ を $ n $ だけ減らし, $ (y-x) $ 増加を実行し続けることを考える. $ (y-x) = 0 $ の時は明らかに $ t $ がいくつでも"Yes".そうでないならやはり $ (y-x) $ 増加の実行できる回数を $ m $ として $ k-m(y-x) \geq l $ より $ m \leq \left\lfloor{\frac{k-l}{y-x}}\right\rfloor $ となり,右辺が $ t $ 以上なら"Yes".

$ y-x > 0 $ の場合

$ y $ リットル足すと水の量が $ r $ リットルを超えてしまうため $ (y-x) $ を選択できない状態をX-Critical,逆に $ x $ リットル引くと水の量が $ l $ リットルを割ってしまうため $ (-x) $ を選択できない状態をY-Criticalと呼ぶことにすると $ y+x $ の値によって以下の $ 2 $ つ図のような状況が存在することがわかる.

f:id:spider_0859:20201220225915p:plain
$ y+x \leq r-l $ の場合,X-Critical と Y-Critical の領域は重ならない.

$ y+x \leq r-l $ の場合,X-Critical と Y-Critical の領域は重ならないということは必ずどちらかの選択肢を選ぶことができるため答えが"Yes"になることがわかる.

f:id:spider_0859:20201220225442p:plain
$ y+x > r-l $ の場合,常に X-Critical または Y-Critical で選択肢がない.

$ y+x > r-l $ の場合,常に X-Critical と Y-Critical のどちらかの状態であるため,進み方は一意になる.重なっている領域に入ってしまうと詰みになる.

どちらの場合も進む戦略が結果に影響を及ぼさないことがわかるので,実装は統一して行う.

何も考えない手法として, $ t $ 回 $ (y-x) $ と $ (-x) $ の選べる方を選択することを繰り返すシミュレーションが挙げられる.当然この計算量は $ O(t) $ で遅い.ここで,制約の中で $ x $ が唯一 $ 10^{6} $ 以下の制約であることに着目する.

めいっぱい $ (-x) $ を選択する → めいっぱい $ (y-x) $ を選択するを繰り返すこと考える. $ (-x) $ をめいっぱい選択するとX-Criticalになる.X-Criticalの領域幅は $ x $ であるため,ここの訪問履歴は十分小さい空間計算量で保持できる.詰むことなく X-Critical 領域内で訪問済みの水量にたどり着いたとき同じことの繰り返しになるので,シミュレーションを打ち切って"Yes"としてよい.

具体的な実装は省くが,片方の選択をめいっぱい実行するのは既に何度も記事内でやっているような立式・式整理を行えば $ O(1) $ で行うことができ,ループ回数は最大でも $ O(x) $ であるから,全体の計算量も $ O(x) $ となり十分高速である.

個人的なポイント

  • 操作をグッと睨んで操作の本質が正負によって変わる場合は別々に考える
  • 一次不等式から条件を満たす最大整数を得る部分問題は冷静に式整理してからコードに落とす
    • 式の整理が難しい場合は条件式をそのまま書ける二分探索を使うことも辞さない
  • YesNo問題は自明なYesやNoを発見することで,自明でないケースの条件を絞り込む
    • 今回は $ y+x \leq r-l $ の時は自明に"Yes"だった
  • 不自然に $ 1 $ つだけ小さい制約 $ (10^{6}以下) $ があったら鳩ノ巣を疑っていい
    • 今回は $ x $ が不自然に小さかった

ソースコード

提出コード

▶ソースコードを展開

AtCoder Grand Contest 049 B - Flip Digits

AGC-Bにしては大分簡単な問題だった気もしますが,だからこそ瞬殺できるようにしたいです. 考察をまとめておきます.

atcoder.jp

問題概要

$ 0 $ と $ 1 $ からなる長さ $ N $ の文字列 $ S, T $ が与えられて $ S $ に対して以下のような操作を任意の回数行える.

  • $ S_i = 1 (2 \leq i \leq N) $ を選択する.$ S_i $ を $ 0 $ に, $ S_{i-1} $ を $ 1 $ なら $ 0 $ に $ 0 $ なら $ 1 $ に置き換える.

この操作を任意回行って $ S $ と $ T $ を一致させることができるならその最小回数を答えよ.

制約

  • $ 1 \leq N \leq 5 \times 10^{5} $

考察

まず操作の内容を考えてみる.

$ S_i = 1 $ のような $ i $ に対して操作を行うことができ, $ S_{i-1} = 0 $ のときは $ 1 $ を $ 1 $ つ左に移動する操作だと言い換えられる.一方, $ S _ {i-1} = 1 $ のときは隣接する $ 1 $ を $ 0 $ に変える操作だと言い換えられる.

したがって操作によって $ 1 $ を左に移動することと,隣接する $ 1 $ を $ 0 $ に変えることのみが可能だとわかる.

この考察から「 $ T $ にある $ T_i = 1 $ を $ S_j (i < j) = 1 $ を満たすなるべく近い $ j $ とマッチングしたい」と考えることは自然だと思う.ただし,この「マッチング」とは $ S $ 中の $ 1 $ を目標の位置までもっていくことを指して言っている.

f:id:spider_0859:20201117012351p:plain:w450

ただし上のような例があるように,最も近いものとマッチングすると余ったもの同士が消せないことが起こり得る.これは左側に $ 1 $ が余った状態でマッチングをしてしまったためである.そもそも $ S $ と $ T $ が一致するためには $ T $ の一番左の $ 1 $ より左側にある $ S $ 中の $ 1 $ は必ず $ 0 $ にしておく必要がある

$ 1 $ を $ 0 $ にする手段は隣接する $ 1 $ を両方 $ 0 $ にする以外に存在しないだめ, $ 2 $ 個ずつ $ 0 $ に変えていく必要がある. $ 0 $ にしなければならない $ 1 $ がなくなったらその時点で $ T $ と $ S $ の一番左の $ 1 $ と同士でマッチングすることができ,これが一番近い(下図).

f:id:spider_0859:20201117132700p:plain:w450

$ 1 $ を余分に偶数個 $ 0 $ に変えてからマッチングさせることも可能だが,可能な限り一番近くの $ 1 $ 同士をマッチングさせることが最適である.この証明は割愛するが遠くにマッチングすることで解が改善することはないことを示せばいいので,そう難しくはないだろう(自明と言えるほどでもない気がするが…).

以上のような手続きを $ T $ の $ 1 $ が全てマッチング済みになるまで続けて矛盾が生じた場合に $ -1 $ を出力して終了すればよい.左端から $ 1 $ 個や $ 2 $ 個取り出す操作をしたいため $ 2 $ つの $ queue $ などを用いて実装すると楽かもしれない.

別解(?)

基本的な考え方は上の解法と同じ.ただ最適な解を求めるもう一つの方法が考えられる.実は最初に示したダメな最も近いマッチングの例のまま残った $ 1 $ 同士を $ 0 $ に変えていけば最適解と同じ操作回数が得られる.

場合分けによって簡単に説明する.

  • 最も近いマッチングで左側に余る $ 1 $ の数が偶数個だった場合明らかに上で示した最適解と同じである.

  • 最も近いマッチングで左側に余る $ 1 $ の数が奇数個だった場合,$ 1 $ を消す過程で右側にあった $ 1 $ が左側の $ 1 $ を必ず追い越す.この追い越しは操作上不可能だが手順を弄れば同じ操作回数で追い越しなしで必ず実現できる.

以上よりこちらの解法でも最適解が得られる.

個人的なポイント

  • 式によって操作内容が書かれている場合はわかりやすく言い換えができないか考える
  • 必ず満たすべき条件を見つけていくことによって状況を絞り込める
    • 今回はどれともマッチングできない $ S $ 中の $ 1 $ 削除しなければならないことから最適解を導けた
  • 貪欲はちゃんと正当性を確認する(今回実はたいして確認していなかったので反省)
  • フリップの問題なので $ xor $ を考えるという筋も存在したようだ.今後のために発想としては持っておきたい

ソースコード

提出コード1

提出コード2

▶ソースコードを展開

AtCoder Grand Contest 049 A - Erasing Vertices

かなり苦手な問題で困ってしまっていました.期待値に関する問題です.

atcoder.jp

問題概要

$ 1 $ から $ N $ までの番号が付けられた $ N $ 頂点からなる有向グラフが,隣接行列の形で与えられる (自己ループや多重辺はない).

このグラフに対して以下の操作をグラフが空になるまで繰り返す.

  • 削除されていないグラフの頂点を一様ランダムに選び,有向辺を辿ってその頂点から到達可能な頂点を全て削除する.

上記の操作が行われる回数の期待値を求めよ.

制約

  • $ 1 \leq N \leq 100 $

考察

まず,$ N $ の大きさから操作手順を全探索するのは不可能.また,操作回数が $ x $ 回であるような操作手順の期待値を多項式時間で求めるのは難しいように見える.そこで,数え上げの典型っぽく頂点ごとの期待値への寄与 (その頂点が手順中で選ばれる回数の期待値) を足し上げていくことを思いつく.

恐らく競プロ典型っぽいのでそのまま進んでもいいが,自分はこういうのが苦手で混乱するタチなので確率変数を導入して考えたものを書き残しておく.

  • 確率変数 $ X_i = 頂点iが選ばれた回数 ( 1 \leq i \leq N ) $

と定義すると,求める値は $$ E \left[\sum _ {i=1}^{N} X_i \right] = \sum _ {i=1}^{N} E \left[X_i \right] $$ と書ける.ここで期待値の線形性に基づく変形を行っている.この式から先述した各頂点の寄与を足し上げた値が答えだと確認できる.

次に $ E \left[X_i \right] $ を求めることを考える.各手順に於いて $ 1 $ つの頂点を選ぶ回数は高々 $ 1 $ 回であるから, $ E \left[X_i \right] $ は「手順中に頂点 $ i $ が選ばれる確率」と言い換えることができる.

有向辺を辿って頂点 $ i $ に到達可能な頂点の集合を $ S(i) $ とすると,手順中に頂点 $ i $ が選ばれるためには $ S(i) $ に属する頂点の中で一番最初に頂点 $ i $ が選ばれる必要がある.条件を満たす確率は $ E \left[X_i \right] = \frac{1}{|S(i)|} $ である.これも自明に感じる人も多いだろうが,自分は混乱するので下に詳しめに書き残しておく.


任意の操作手順は正則表現っぽく「任意回の $ \overline{S(i)} $ からの頂点選択 + $ 1 $ 回の $ S(i) $ からの頂点選択 + 任意回の頂点集合 $ V $ からの頂点選択」と表わせる.この表現の「 $ 1 $ 回の $ S(i) $ からの頂点選択」の部分を「 $ 1 $ 回の頂点 $ i $ の選択」とすることによって任意の頂点 $ i $ が選ばれる操作手順を表すようになり,その数は元の $ \frac{1}{|S(i)|} $ となることが分かる.これは $ v \in \overline{S(i)} $ を選択した際 $ S(i) $ に属する頂点は削除されず,初めて頂点 $ i $ が選ばれるまで $ S(i) $ に属する頂点はどれも削除されていないため.


$ E \left[X_i \right] = \frac{1}{|S(i)|} $ が分かった.$ |S(i)| $ は逆辺を張ってDFSをして到達可能な頂点の数であるため,$ O(N^{2}) $ で求められる.$ 1 \leq i \leq N $ についてこれを求めて足し上げるので全体の計算量は $ O(N^{3}) $ になり,十分高速である.

別解

最終的な式は同じだが,もう少しわかりやすい考察手法がある.

問題を次のように言っても意味が同じである.

  • $ 1, 2, \cdots, N $ をランダムに並び変えて出来る列 $ p $に対して,$p_0, p_1, \cdots, p_N $ の順に頂点を見ていき,削除されていなければ操作回数を $+1 $ してその頂点とそこから辿り着ける頂点を全て削除する.

こう言いかえることによって,条件を満たす順列の数え上げの問題になる.具体的には「$ i $ が $ S(i) $ に属する他のどの頂点よりも前に来るような順列である確率」が $ E \left[X_i \right] $ と等しくなり,先ほどより比較的簡単に $ \frac{1}{|S(i)|} $ となることが分かると思われる.

個人的なポイント

  • 期待値の問題が出て問題文通り素直に考えて難しいなら,線形性を利用した変形をして様子をみる.
    • そもそも基本的に数え上げと本質は同じなので主客の入れ替えは典型として頭に入れておくべき.
    • 操作の言い換えによって扱いやすい形にするのも一手
  • 多分自分は場合の数系の自明っぽい計算が苦手なので,徹底して分解する.
  • $ i $ に到達可能な頂点数は逆辺を張って簡単なDFS木DPをする.

提出URL

▶ソースコードを展開

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

▶ソースコードを展開