Atcoder: みんなのプロコン2019

published: ,
last modified: .

lang: ja

category: competitive_programming

tags: atcoder ocaml

A. Anti-Adjacency

選ぶ数の最大値をなるべく小さくしたいことから,1から1つ飛ばしで数を選んでいくのが最適.このときkk個目(k=1,...)(k=1,...)の数はkk番目の正奇数,すなわち2k12k-1であるから,これとnnとの大小を比較する. 解答例

B. Path

問題文の処理をそのまま実装するDFSを書いた.道に番号を振ってそれぞれの道を通ったかを配列で記録しておけば,3回の移動で「すべての道を」「1回ずつ」通ったかは迷いなく判断可能. 解答例

editorialの解法は漏れを作りそうなため,個人的にはこちらの解法のほうが好ましく思う.

C. When I hit my pocket...

こういう操作の特性を考える系の問題はあまり得意ではない.実験をした. 解答例

本文中にある操作を上からP,Q,Rとする.最終的に円を持っていても利益がないので操作Q,Rの数は同数.すると例えば操作列はPPPQRPPQQRRP...のようになるが,P...PQR...QRと整列して構わない.このときPの数をppQRの数をqqとすると,p+2q=kp+2q=k. このときのスコアはp+1+(ba)qp+1+(b-a)q =(k2q)+1+(ba)q= (k-2q)+1+(b-a)q =(ba2)q+k+1= (b-a-2)q+k+1

qqの係数が負または0,すなわちba2b-a \le 2の場合はqqが少ないほうがよく,q=0q=0としてスコアはk+1k+1.そうでない場合はqqは多いほうが良い.ただし操作Qを行う条件としてpa1p \ge a-1,変形してqka+12q \le \frac {k-a+1} {2}が必要であり,q=ka+12q = \lfloor \frac {k-a+1}{2} \rfloorとしてスコアを算出すればよい.ここでq0q \ge 0にも注意:qqが負のときはq=0q=0とする.

最初はppについて整理しようとして散々な目に遭った.q=kp2q= \frac{k-p}{2}を用いるとp+1+(ba)qp+1+(b-a)q =p+1+(ba)(kp)= p+1+(b-a)(k-p) =p(1(ba))+1+(ba)k= p(1-(b-a))+1+(b-a)kba>1b-a \gt 1のときはppの係数が負なので操作Q,Rを増やすべきだが,操作Qを行う条件としてpa1p \ge a-1.素直にp=a1p=a-1としたいが,qqは整数なのでk(a1)k-(a-1)の偶奇で場合分け:偶数のときはそのままq=k(a1)2,p=a1q=\frac {k-(a-1)}{2}, p=a-1.奇数のときはq=k(a1)2q = \lfloor \frac{k-(a-1)} {2} \rfloor (=ka2)(=\frac {k-a} {2})としてしまうと1操作余るため,それを操作Pに回してp=a,q=ka2p=a, q=\frac {k-a} {2}とする.というように,ppについて整理するほうが面倒.

ところでeditorialでは最初からba2b-a \le 2かを見ている.これは操作QR(スコア+(BA)+(B-A))と操作PP(スコア+2+2)の比較と見れば納得できる:ba2b-a \le 2であれば後者を選んだほうがよく,ba2b - a \ge 2であれば前者を選んだほうが得をする.

D. Ears

散歩が1回だけという条件を読み落としていたが,そうでなくても解けなかったと思う.

解説(editorialなど)

まず散歩により耳がどうなるかを考えると,これは[00]*-[偶数2\ge 2]*-[奇数]*[1]-[偶数2\ge 2]*-[00]*という55つの区間が並んだ形になる.逆にこの条件下であれば任意数の石を入れることができる[2]

この結果の導出についてはこちらの記事が大変参考になった:始端\to終端という一番単純な形から考えてみる.この2点を固定すると,散歩のバリエーションとしてあり得るのは途中箇所に往復を加えたものだけであるから増分は必ず偶数となり,ここから導かれる[3]. 以下では当該記事も含めた2記事を参考文献としている(後掲する).

本問で行う操作は次の2つ:

  1. 散歩によって位置iiの石の個数をb[i]b[i]とする
  2. b[i]b[i]に好きな回数だけ±1\pm 1をし,b[i]=a[i]b[i]=a[i]とする

各位置iiが散歩のときどの区間だったのかを考える.先に述べた区間を順にZl,El,O,Er,ZrZ_l,E_l,O,E_r,Z_rとする. 前の数字が区間AAであるとすると区間の順序から位置i+1i+1AA以降の種類のどれかになるはずであり,逆にどれになっても構わない[4]

位置iiが当て嵌まる区間の種類を仮定すると,a[i]a[i]の値から必要な操作の回数もわかる(先に述べたようにb[i]b[i]が好きなように作れるため):

  • a[i]a[i]ZlZ_l: b[i]=0b[i]=0からa[i]a[i]回操作を行ってa[i]a[i]にしたとするのが最小
  • a[i]a[i]ElE_l: b[i]b[i]は2以上の偶数であることから,
    • a[i]=0a[i]=0のとき操作は22回(b[i]=2b[i]=2として2-2する)
    • 22以上の偶数のとき00回(そのまま)
    • 奇数のとき11回(b[i]=a[i]+1b[i]=a[i]+1とすると,操作で1-1することでa[i]a[i]が作れる)
  • a[i]a[i]OO: b[i]b[i]は奇数であるから,
    • a[i]a[i]が奇数であれば操作は00
    • 偶数であれば11回(b[i]=a[i]+1b[i]=a[i]+1とする)
  • a[i]a[i]ErE_r: ElE_lと同様
  • a[i]a[i]ZrZ_r: ZlZ_lと同様

肝心の区間を決めたい.以前に「区間の境界を1つ決めると残りの境界も決まる」というタイプの問題を見たことがあるが今回はたぶんそうはならない.DPができる:dp[i][K]dp[i][K] \coloneqqa[i]a[i]が区間KKであるときの操作回数の最小値」とすると,先に説明したように区間の遷移を書ける.すなわち区間KKに対して先の操作回数をfK(a[i])f_K (a[i])KKまたはそれ以前の区間の種類をP(K)P(K)とおくと:

dp[i+1][K]minJP(K){dp[i][J]+fJ(a[i])}dp[i+1][K] \gets \min_{J \in P(K)} \{ dp[i][J] + f_J (a[i]) \}

となる.配るDPとして解釈したほうが先の記述に合っているが,配るDPを式で書くときの慣例がよくわからず. 計算量はKKが5通りなのでO(L)O(L).考察さえ済めばかなり綺麗に書ける. 解答例

参考

  1. https://betrue12.hateblo.jp/entry/2019/02/10/014031
  2. http://koba-e964.hatenablog.com/entry/2019/02/10/003030

  1. 始端=終端のとき長さ00もあり得る. ↩︎

  2. 怪しい説明:例えば現在の散歩範囲に含まれる区間[p,q][p,q]+4+4したい場合を考える.ppに着いたときにpqpqpp \to q \to p \to q \to pを行えば+4+4かつ元通りppに戻る.加えたい数字が変わるところで区間を切ってそれぞれに対して同様の処理を行えば互いに独立になるので大丈夫そうな気がしてくる. ↩︎

  3. このように考えたほうが散歩範囲の左端と右端が現れる理由が若干明確になる気はする. ↩︎

  4. ZlZ_lZrZ_r,およびElE_lErE_rは条件として同じであるから,例えば区間KKについてKZlK \to Z_lKZrK \to Z_rではより自由度の高い左側だけを考えてもよい.とはいえ実行時間的にはわずかに早くなるだけだった(オンラインジャッジで実行時間が計れるかは微妙ではある). ↩︎