目次

ユニークビジョンプログラミングコンテスト2023 クリスマス (AtCoder Beginner Contest 334) F,G問題メモ

ユニークビジョンプログラミングコンテスト2023 クリスマス (AtCoder Beginner Contest 334)

12/23開催なだけあって、クリスマスにちなんだ問題が多くてよき。

F - Christmas Present 2

F - Christmas Present 2

問題

解法

ふんわりと、以下のようなDPでできそう。

$K$ 個までしか積めない=最後に家に戻ったのが $K$ 軒前まででないといけない。
よって、$DP[i]$ を求めたいとき、(DP配列をセグメント木などで実装して) $DP[i-K]~DP[i-1]$ の最小値を得たら $DP[i]$ が求まる、というようなことがしたい。

ただ、それだと上記の定義のままではダメ。
たとえば3軒目を訪ねるとき($DP[3]$ を埋めるとき)

DP[0]:  サ→①→②→③→...
        ~~                             ←DP[0]の値が示す移動

DP[1]:  サ→①→サ→②→③→...
        ~~~~~~~~~~                     ←DP[1]の値が示す移動
        
DP[2]:  サ→①→②→サ→③→...
        ~~~~~~~~~~~~~~                 ←┬小さい方がDP[2]の値が示す移動
  または                                 │
        サ→①→サ→②→サ→③→...      │
        ~~~~~~~~~~~~~~~~~~             ←┘

※サ: サンタの家

DPの示す値の最終位置がサンタの家だと、そこから3軒目に行くまでの距離がそれぞれ異なってしまうので、比較できない。

最終位置は、$DP[3]$ を求める上では3軒目である必要がある。よって、DPを以下で定義しなおして、

DP[3,0]:  サ→①→②→③→...
          ~~~~~~~~~~~~~~

DP[3,1]:  サ→①→サ→②→③→...
          ~~~~~~~~~~~~~~~~~~

DP[3,2]:  サ→...→②→サ→③→...
          ~~~~~~~~~~~~~~~~~~~

これなら同条件で比較でき、$DP[3,0]~DP[3,2]$ の最小値が、3軒目に配るまでの最短距離となっている。

次、1つ進めて $DP[4,*]$ を求めたいとき、

DP[4,0]:  サ→①→②→③→④→...
          ~~~~~~~~~~~~~~~~~~

DP[4,1]:  サ→①→サ→②→③→④→...
          ~~~~~~~~~~~~~~~~~~~~~~

DP[4,2]:  サ→...→②→サ→③→④→... (一例)
          ~~~~~~~~~~~~~~~~~~~~~~~

DP[4,3]:  サ→.......→③→サ→④→... (一例)
          ~~~~~~~~~~~~~~~~~~~~~~~

$DP[4,0]~DP[4,2]$ に関しては、$DP[3,0]~DP[3,2]$ に新たに③→④の距離を加算すればよい。
$DP[4,3]$ に関しては、$DP[3,*]$ の最小値に ③→サ→④ の距離を加算した値を新規追加すればよい。

これで4軒目までの最短距離も求められた。後は繰り返し。

実装上は $i$ の次元は省略して破壊的に更新していくとして、 「区間加算更新」と「区間最小値クエリ」を処理できる遅延評価セグメント木でDPを実装すれば $O(N \log{N})$ で解ける。

$N+1$ 軒目に便宜的にサンタの家を入れておけば、$i=N+1$ まで回した後の $DP[N]$ が答え。

Python3

G - Christmas Color Grid 2

G - Christmas Color Grid 2

問題

解法

期待値の分母は「初期状態の黒マス数」でわかりきっているので、 分子である「各黒マスを消したときの連結成分数の総和」が求まればよい。

黒マスを頂点と見なし、隣接した黒の間に辺を張ったグラフで、関節点と橋を求める。

これらは、lowlink という概念で、1回のDFSでまとめて求められる。

初期状態の連結成分数を $C$ とする。

関節点でない場合

「関節点=そいつを消したら連結成分数が増える頂点」なので、逆に関節点でなければ連結成分は増えず $C$ のまま変わらない。

ただしそいつが、元から1マスで1つの連結成分を構成していた場合、1減って $C-1$ となる。

関節点の場合

関節点なら、そこから出ている辺の本数と、橋の本数を見る。

(a)全ての辺が橋なら、連結成分は「橋の本数-1」増えて  C+橋の本数-1

   ###            ###
    |
###-#-###  →  ###   ###
    |
   ###            ###


(b)そうで無い場合、橋があるなら、橋の本数分増える。 C+橋の本数

#####          #####      橋でない連結成分が1つ残ったまま、
#   |          #          橋の本数分だけ新たな連結成分ができる
###-#-###  →  ###   ###
    |
   ###            ###


(c)橋が0本の場合、必ず以下のような形となっている。
2本ずつ出る辺が、それぞれペアになってくっついている。

#####          #####
#   |          #
###-#-###  →  ###   ###
    |   #              #
    #####          #####

よって、この場合は真ん中を消すと2つに分かれ、連結成分数は1増えて C+1 となる。

(b)(c) の場合、連結成分の増減は、グリッドグラフに限った話である点に注意する。
この問題ではよいが、一般のグラフだと成り立たない。

グリッドグラフの最大次数が4であることを利用しているので、 例えば三つ葉のクローバー(♣の茎を取った形)のようなグラフの真ん中(次数6)を消すと、橋が0本と (c) と似たような状況ではあるが3つに分かれる。

一般の場合、関節点と橋の情報だけでは計算できないが、それらを求める際に計算した、DFS木とlowlinkに立ち返ればチェックできる。(公式Editorial参照)

Python3