目次

AtCoder Beginner Contest 259 E,G,Ex問題メモ

AtCoder Beginner Contest 259

E - LCM on Whiteboard

E - LCM on Whiteboard

問題

解法

LCMは、各素因数 $p$ につき、$N$ 個の数の中で指数 $e$ が最大のものを持ってきてかけ合わせるとできる。

p\ai  49    42    20    21        2940 = LCM
2      0     1     2     0    →    2
3      0     1     0     1    →    1
5      0     0     1     0    →    1
7      2     1     0     1    →    2

「1つ選んで1に書き換える」操作をする前のLCMを「元のLCM」として、 操作したときに元のLCMから変化するのは 「$a_i$ の素因数の中に、その素数に対する指数が $a_1,...,a_N$ の中で唯一の最大であるようなものが含まれている」場合である。

p\ai  49    42    20    21
2      0     1    [2]    0
3      0     1     0     1      ←唯一の最大でない
5      0     0    [1]    0
7     [2]    1     0     1

上記、$p=2$ は、20を消せば最大が変化するので、結果のLCMも独自のものに変化する。
一方、$p=3$ は、42を消しても21が残ればLCMの指数は1であり続けるし、逆も同じ。LCMは元から変化しない。

このように、指数が唯一の最大である素数を素因数に持つ $a_i$ を「変化数」と呼ぶことにする。
上の例では49と20がそれに当たる。

素因数分解は一意なので、異なる変化数を消したときの結果が同じになってしまうことはない。
上記で20を消したときのLCMと49を消したときのLCMは、必ず異なる。

よって、変化数の個数を数えればよい。
20における2と5のように、同じ変化数の中に指数最大の素数が複数含まれていても、カウントは1回。

あとは変化数でない $a_i$ が存在すれば、LCMが元から変わらない場合を+1すると答えとなる。
これは、変化数の個数が $N$ 未満かどうかで判定できる。

Python3

G - Grid Card Game

G - Grid Card Game

問題

解法

燃やす埋める。言われてみれば。

選ぶ行・列を決め打ったとき、 結果は「選んだ行の $A_{i,j}$ の総和 + 選んだ列の $A_{i,j}$ の総和 - 選んだ行と列がクロスする部分の $A_{i,j}$ の総和」となる。

クロス部分を引くのは、行と列で総和に2回足されてしまっているのの調整のためだが、 今回はこの部分の $A_{i,j}$ に、負値が1つでもあってはいけないという制約がある。

まず行・列ごとに総和を取って、それが0以下になる行・列については、選ぶ必要は無い。

残った行・列に対して選ぶ・選ばないを割り振っていくのだが、

とまとめられ、燃やす埋める問題に帰着でき、最小カットで解ける。

通常の燃やす埋めるでは、2項間の関係について「$a_i$ を埋め、$a_j$ を燃やせば○○のコスト」のように、 2つで異なる選択肢をとったときのコストしか表現できない。
この問題では「ともに選ぶ」ことで発生するコストがあるが、これは行と列で左右の意味合いを反転させることで対応できる。

   ,-- 行① --,       最小カット問題の文脈で、
   |          |       行の頂点は、S側に属することを「選ぶ」
   |-- 行② --|                   T側に属することを「選ばない」
   |          |       列の頂点は、S側に属することを「選ばない」
S-|-- 行③ --|-T                T側に属することを「選ぶ」 と意味づける
   |          |
   |-- 列① --|       S→行 に各行の総和のコストを設定
   |          |       列→T に各列の総和のコストを設定
   `-- 列② --'       行→列 にクロスする箇所のコストを設定

頂点数 $O(H+W)$、辺数 $O(HW)$ の最大流問題となり、制約的に十分高速に解ける。

Python3

Ex - Yet Another Path Counting

Ex - Yet Another Path Counting

問題

解法

複数のラベルをまとめて算出、というのはかなり難しそうなので、ラベル毎に答えを求めていく。

解き方が2種類あって、 マスが疎(個数が少ない)時に高速な方法と、密(個数が多い)時に効率的な方法を使い分ける。

1つの解法が浮かぶと、それをどうにか高速化する方法を考えてしまい、2つめの解法の存在に気づきにくい。

解法①

ラベルが同じ起終点ペアを全探索。

右と下移動のみで到達できない位置関係ならパス数は0。
下に $r$ 回、右に $c$ 回移動するなら二項係数 ${}_{r+c}C_r$ がそのペア間のパス数。これを全ペアで合計する。

1ラベルあたりの計算量は、そのラベルが設定されたマスの個数を $L_i$ として、$O(L_i^2)$。
全体ではこれをラベル毎に合算した計算量がかかる。

解法②

よくある経路数数え上げDP。
左上から順に、「そのマスのすぐ左と上の値を足し合わせる」やつ。

全てが $0$ の状態から始め、 $A_{i,j}$ が着目中のラベルであるときには、そこからスタートするパスが新たに発生するので+1する。

最終的に、着目中のラベルが設定されたマスの $DP$ 値の合計が、そのラベルに関する総パス数。

1ラベルあたりの計算量は、そのラベルのマスの個数に依らず、$O(N^2)$。(枝刈りできるっちゃできる)

場合分け

解法①では、例えば同じラベルを持つマスの個数 $L_i$ が、各ラベル $L_i=L$ で大体同程度な場合は ラベルの種類数が $\dfrac{N^2}{L}$、全体の計算量は $O(N^2L)$ となる。$L$ が小さいときは十分通る計算量であることがわかる。

解法②ではラベルの種類数のみに依存するので、$L$ が大体同程度な場合、 ラベルの種類数が $\dfrac{N^2}{L}$、全体の計算量は $O(\dfrac{N^4}{L})$ となる。$L$ が大きければよさそうなことがわかる。

$O(N^2L)$ と $O(\dfrac{N^4}{L})$ のバランスが取れるのは $L=N$ の時であり、ここを基準に処理を振り分ければよさそう。

上記の例では $L$ が同程度の時しか説明できていないが、それ以外の場合でも、$L \le N$ を基準にすると

①は、$\displaystyle \sum_{各ラベル} L_i^2 \le \sum N \cdot L_i = N \sum L_i$ となり、 $\sum L_i$ は、つまりこの処理を適用する総マス数なので $N^2$ 以下。$N \sum L_i \le N^3$。

②は、$N$ 以上のマス数があるラベルは多くとも $N$ 種類以下しかないので、1種類あたり $O(N^2)$ なら全体で $O(N^3)$。

となり、いずれも $O(N^3)$ に収まることが確認できる。①の解析の仕方が上手。

Python3