Processing math: 100%

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

E - LCM on Whiteboard

問題

  • ホワイトボードに N 個の整数 a1,a2,...,aN が書かれている
  • 整数はとても巨大なことがあり、素因数分解した形で与えられる
    • mi 個の素数を用いて ai=pei,1i,1pei,2i,2...pei,mii,mi
  • ai のうち1つを選んで 1 に書き換えたときに、N 個の総最小公倍数として取り得る値の個数を求めよ
  • 1N2×105
  • 1mi2×105

解法

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から変化するのは 「ai の素因数の中に、その素数に対する指数が a1,...,aN の中で唯一の最大であるようなものが含まれている」場合である。

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は元から変化しない。

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

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

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

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

Python3

G - Grid Card Game

問題

  • H×W のグリッド状にカードが配置されている
  • ij 列目にあるカードには整数 Ai,j が書かれている
  • いくつかの行といくつかの列を選ぶ(0個でもよいし、全てでもよい)
  • ただし、Ai,j が負であるとき、i 行目と j 列目をともに選ぶことはできない
  • 選んだ行と列に置かれたカードの Ai,j の総和の最大値を求めよ
  • 1H,W100
  • 109Ai,j109

解法

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

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

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

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

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

  • i 行目を選ぶと i 行目の総和の利益
  • j 列目を選ぶと j 列目の総和の利益
    • 利益は前もって獲得したものとして、「選ばないと○○のコスト」と言い換えられる
  • Ai,j<0 のとき、i 行と j 列をともに選ぶと のコスト
  • Ai,j0 のとき、i 行と j 列をともに選ぶと Ai,j のコスト

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

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

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

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

Python3

Ex - Yet Another Path Counting

問題

  • N×N のグリッドの各マスに、ラベル Ai,j が設定されている
  • あるマスから「0 回以上の右または下への1マス移動」を繰り返してあるマスで移動を終える経路を「パス」と呼ぶ
  • 出発マスと到着マスのラベルが同じパスの個数を mod998244353 で求めよ
  • 1N400

解法

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

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

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

解法①

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

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

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

解法②

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

  • DP[i][j]=(i,j) に至るパスで、始点のラベルが現在着目中のラベルであるものの個数

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

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

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

場合分け

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

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

O(N2L)O(N4L) のバランスが取れるのは L=N の時であり、ここを基準に処理を振り分ければよさそう。

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

①は、L2iNLi=NLi となり、 Li は、つまりこの処理を適用する総マス数なので N2 以下。NLiN3

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

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

Python3

programming_algorithm/contest_history/atcoder/2022/0709_abc259.txt · 最終更新: 2022/07/13 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0