Australia

ウォンバットのブログ

CODE FESTIVAL 2018 qual A D - 通勤

これを通せたおかげで予選通過できました。嬉しい。

解法

 X_0=0とします。DPをします。

 dp[i][j]:=ガソリンスタンド iまで来るにあたって、ガソリンスタンド jで最後に補給するとしたときの、そこまでのガソリンスタンドを書店に建て替えるパターン数

次の3つを考える必要があります。

  • 近くのガソリンスタンド jで補給をしていてガソリンスタンド iで補給ができないとき、ガソリンスタンド iを書店に建て替えても建て替えなくても変わらない
  • やや遠くのガソリンスタンド jで補給をしていてガソリンスタンド iで補給ができるとき、ガソリンスタンド iを書店に建て替えるなら最後に補給した場所は変わらず、建て替えないなら最後に補給したガソリンスタンドは iになる
  • めっちゃ遠いガソリンスタンド jで補給をしていてガソリンスタンド iまでたどり着けないとき、ガソリンスタンド jまでの情報は無意味になる

まとめると、次のようになります。

  dp[i][j]= \begin{cases}
    \displaystyle \sum_{F - T< X_i-X_{j'} \leq F} dp[i-1][j'] & (i=j) \\ 
    2dp[i-1][j] & (0 < X_i-X_j \leq F - T)\\
    dp[i-1][j] & (F - T< X_i-X_j \leq F)
  \end{cases}

これを愚直に処理すると O(N^2)となりTLEします。データを使いまわしてオーダーを落とすことを考えましょう。
列の末尾 n項を 2倍し、その前の m項の区間和を末尾に追加するという操作ができればいいわけです。
さらに、項は「 2区間」→「区間区間」→「無意味な区間」と移動する事を利用し、それぞれの区間をqueueで持ち、区間和も持っておけば、遷移が高速化できます。要は尺取法なので、全体の計算量は O(N)となります。

反省

ARC070 Eでやったのと似たようなDPの高速化でした。勉強の成果が出せてよかったです。
この回のC問題と同じく、方針はすんなり立てられたように思います。DPは得意分野にしていきたいです。
そして何より


精進します!

CODE FESTIVAL 2018 qual A C - 半分

解法

 A_i=0になったら、それ以降は 2で割らないものとします。そうすると、求める答えは本来の「 K回の操作後の数列としてありうるものの個数」に「 K回未満の操作後の 0 1つ以上含む数列としてありうるものの個数」を加えたものになります。

 dp[i][j][k]:= A_1から A_iまで考えたときの、 j回操作してできる数列の個数( kはそれまでの要素に 0があったかどうかのフラグ)

というDPを考えれば、 A_i 2 c_i回割ると 0になるとして、

 dp[i][j][0]=\displaystyle \sum_{j'=j-c_i+1}^{j} dp[i-1][j'][0]
 dp[i][j][1]=\displaystyle \sum_{j'=j-c_i}^{j} dp[i-1][j'][1]+dp[i-1][j-c_i][0]

と遷移が定義できます。あとはここから適切に値をとってきて足し合わせればよいです。
計算量は O(N^2 (\log \max A_i)^2)になります。

反省

DPをDPだと気づくのはけっこう速くなったような気がしますが、詳細を詰めるのが苦手です。

AtCoder Regular Contest 100 F - Colorful Sequences

解法

「全ての列についての Aに一致する連続する部分列の個数の総和」から「カラフルでない列についての Aに一致する連続する部分列の個数の総和」を引く方針で考えます。前者は簡単なので省略します。後者は Aがカラフルな場合は 0なので、以下カラフルでないものとします。
まず次のDPを考えます。

 dp[i][j]=ある特定の長さ iのdistinctな列の後に、 j個の要素を繋げてできるカラフルな列の個数

次が成り立ちます。

 dp[i][j]=\displaystyle \frac {dp[i-1][j+1] - \displaystyle \sum_{i'=1}^{i-1} dp[i'][j]} {K-i+1}

ただし、このDPを初期化するには、 dp[1][j]が分かっていないといけません。そこで、

 dp'[j][k]=ある特定の 1項の後に、 j個の要素を繋げてできるカラフルな列であって、distinctなsuffixの最大長さが kであるものの数

というものを考えれば、

 dp'[j][k]=\displaystyle \sum_{k'=k}^{K-1} dp'[j-1][k']+(K-k+1)dp'[j-1][k-1]

と遷移が定義できることがわかります。 dp[1][j]=dp'[j+1][1]です。これらのDPはいずれも累積和で高速化できます。
さて、以上を使って「カラフルでない列についての Aに一致する連続する部分列の個数の総和」を求めていきましょう。
 Aがdistinctな場合。数列中に長さ Mのdistinctな列が登場する場合の数は、終了位置を jと固定すれば、 K \sum_{k=M}^{K-1} dp'[j-1][k]dp[k][N-j]です。その中に、 Aはちょうど \frac{(K-M)!}{K!}の割合で存在します。あとは j Mから Nまで動かして総和をとればよいです。
 Aがdistinctでない場合。 Aのdistinctなprefixの最大長さを l、suffixの最大長さを rとすると、数列中に Aが登場する場合の数は、開始位置を iと固定すれば、 dp[i-1][l]dp[N-M-i][r]になります。あとは i 1から N-Mまで動かして総和をとればよいです。
計算量は全体で O(NK)になります。

反省

5日かかりました。難しかった。
コンテスト中のACが少ない問題はなかなか解けなくても自己嫌悪に陥らないので良いです。(にしても5日はかかりすぎか)
 dpの遷移を立てるまでにかなりの苦労がありました。しかも蓋を開けてみたら非想定解でした。想定解の方が数倍賢い。

AtCoder Regular Contest 102 F - Revenge of BBuBBBlesort!

自力AC最高点を更新しました!

解法

まず、操作の性質を考えてみると、以下のようなものが浮かび上がります。

  • 一度反転した3項の中央は、その後動かすことはできない
  • 一度動かした項を元の位置に戻すことはできない

一つ目を示します。 i項目を中心に反転させたとします。ここから i項目を動かすには、 i \pm 1項目を中心に反転させる必要がありますが、そのままでは i項目と i \pm 1項目が反転できる大小関係を満たしません。そこで i \pm 2項目を中心に反転させることでこれを解消しても、今度は i \pm 1項目と i \pm 2項目が反転できる大小関係を満たさなくなります。
二つ目は、一つ目の派生です。一度壊した大小関係は元には戻せません。(人間関係と似ていますね)
一つ目の性質から、その後動かさなくていい項、つまり p_i=iなる i項目しか反転の中心にできないことがわかります。二つ目の性質から、このような項はむしろ動かしてはなりません。このような項をピボットと呼ぶことにします。さて、ピボットは操作の過程で増えるでしょうか? たしかに条件を満たす項は増えていきますが、実際にそこを中心とした反転を行うことはできない(動かすことのできない項が必ず隣にある)ため、ピボットは増えないものとしてよいことがわかります。よって、入力は以下を満たす必要があります。

  • 任意の iについて、 i-1, i, i+1項目のいずれかはピボットである

ここで、項を偶数番目と奇数番目に分けたとき、ピボットを区切りとした連続する非ピボット同士で数をやり取りするしかないため、そのような項のみ取り出します。まず、それらが集合として正しくない場合は弾きます。次にそれらを昇順に並べることを考えます。以下のような必要条件が浮かびます。

  •  i \lt j,  p_i \gt p_jを満たし、非ピボットである i, j項目について、区間 (i, j)区間 (p_j, p_i)は共通部分を持つ

つまり、ある2つの項を入れ替えなくてはならないとき、その間にそれらを入れ替えられるピボットが必要という事です。
実はこれは十分条件でもあります。つまり、条件を満たした状態から反転を行っても依然として条件は満たされたままになっています。例えば \cdots , 8, 5, 10, 7, 6, \cdots 5, 7がピボット)のような列において、 7を中心に反転を行うと 8 6が交換できなくなるように思えますが、よく考えると 6より右側に 6より小さい項が1つはあるはずであり、元から条件を満たしていません。反転後に条件を満たさなくなるように思えるのはすべてこのパターンであることがわかります。
では、条件を満たしているかどうかを判定する方法を考えましょう。区間 (i, j)区間 (p_j, p_i)が共通部分を持たないとすると、i \lt p_i j \gt p_jであるといえます。(非ピボットなので等しいことはない)
よって、非ピボットな i項目について、i \lt p_iならばそれより右にそれより小さい非ピボットがなく、i \gt p_iならばそれより左にそれより大きい非ピボットがないとき、条件は満たされています。これは、左からの累積 \max列と右からの累積 \min列を持っておけば、全体として O(N)で判定することができます。

反省

初めてARC-Fなるものを自力で通し、根気よく考察を進めていけば案外解けなくもないことがわかりました。
思考過程はほぼ解法に書いた通りですが、「項を偶奇で分けてピボットで区切った非ピボットたちが集合として正しくない場合は弾く」あたりが少しだけ違います。(といっても、予めそういうのが弾かれるような条件を考えてあったというだけです(ACコードはそっちの実装になっています))

AtCoder Regular Contest 102 D - All Your Paths are Different Lengths

これは流石に解きたかった

解法

  • 頂点 iから頂点 i+1に長さ 0の辺と長さ 2^{i-1}の辺を張る

とすると、経路長 0から 2^{N-1}-1のパスが1本ずつできます。
まずはこれでパスの最大長さを Lを超えない最大まで持って行きましょう。そこから、

  • 頂点 iから頂点 Nに長さ  現在あるパスの最大長さ+1の辺を張る

とすると、パスの最大長さが 2^{i-1}だけ更新されることがわかります。したがって、 L-2^{N-1}の二進数における各桁を取り出しながら、適切に辺を張り最大長さを更新していけばよいです。

反省

解法の上2行まではすぐでしたが、そこから先の考察がお粗末すぎました。
あくまで二進数を詰める方向で行けばよかったのですが、途中でこれでは無理だと勘違いして基数を変えてみたりしていました。
最初からある程度道が開けていただけに変に焦ってしまったところはあると思います。
しかしながらやっぱり

AtCoder Regular Contest 070 E - NarrowRectangles

考察してて生えてきた図に既視感があると思ったら、以前諦めて解説だけ見た問題だった

解法

 dp[i][x]=i個目の左端をxとしたときのi個目までの総移動量の最小値

というDPについて考えます。以下が成り立ちます。

 dp[i][x]=\displaystyle \min_{x-(r_{i-1}-l_{i-1}) \leq x' \leq x+(r_i-l_i)} dp[i-1][x'] + |x-l_i|

ここまでで部分点が取れます。
実は dp[i]は、傾きが段階的に変化するU字状の関数になっており、傾きが変わる点の集合をとして管理することができます。具体的には、

  • 傾き 0のところから点を左右に分け、左側を降順、右側を昇順のpriority_queueとして持つ。左右それぞれで点同士の相対位置が正しくなるようにする。傾き 0の線の両端の点だけ正しい座標を持っておく。
  • 漸化式の第1項を処理することを考える。傾き 0の線が広がることがわかるので、両端の点の座標を更新する。
  • 漸化式の第2項を処理することを考える。ある点で傾きが 2変わることがわかるので、priority_queueに同じ点を2つ追加し、左右間で点を受け渡す。座標と答えを更新する。

とすればよいです。
計算量は O(N \log N)となり、満点が得られます。

反省

解説だけ見たものも意外と覚えているらしく、割とすんなりACできました。
データ構造を持ってDPのオーダーを落とすというのは初めてやったように思います。こういうのもあると心の片隅に置いておきます。

AtCoder Regular Contest 066 E - Addition and Subtraction Hard

過去問。
なんかやけにあっさり解けて、順位表見たら通してる人が意外と少なく、ついに僕も覚醒したか? と思ったらXor Sum回だった

解法

正符号の後ろから括弧を開始するのは明らかに無意味です。ある負符号から何項かを括弧で括ることを考えます。(この括弧を外括弧と呼ぶことにします)

 - (11 + 45 - 14 + 19 + 19 - 8 + 10)

ここで、外括弧内の項はなるべく最終的に正にしてやりたいです。

 - (11 + 45 - (14 + 19 + 19) - (8 + 10)) = - 11 - 45 + 14 + 19 + 19 + 8 + 10

このように負符号を区切りとして括るようにすると、与式において正符号が続く最初の何項かを除いて全部正にできることがわかります。
この最初の何項かというのは、外括弧の開始位置を固定して意味のある括り方をしようとすると必ず巻き込まれてしまいます。(必要な犠牲)
外括弧内の後ろの方は正にできるので、終了位置は与式の一番最後にするとよさそうです。外括弧は1つでよいこともわかります。
最終的に負となってしまうのは、開始位置からの何項かと、開始位置より前にあるもとから負だった項です。
ここまでわかれば、あとは与式を前から見ていって開始位置を動かしながら、なるべく大きくなるような答えを探せばよいです。

反省

正の項しかない場合の処理を忘れて1WA食らったものの、ほとんど解法に書いた道筋通りに考察を進め、スムーズに解くことができました。
コンテスト本番でもかくありたいものです。