目次

パナソニックプログラミングコンテスト(AtCoder Beginner Contest 186)D,E,F問題メモ

パナソニックプログラミングコンテスト(AtCoder Beginner Contest 186)

中国剰余定理は何回見てもアルゴリズムが覚えられんのよね。

D - Sum of difference

D - Sum of difference

問題

解法

絶対値は「非負ならそのまま、負なら正負逆転」という操作と言い換えられ、「絶対に非負」という保証を作ってしまえば外せる。

そのためには、常に $A_j \ge A_i$ となればよくて、これは $A_i$ を昇順にソートすれば保証される。

ソートしても、「$A$ の全てのペアについて $|A_j-A_i|$ を求めて合計する」という処理内容は一緒で、 $|A_j-A_i|$ は、$i$ と $j$ を入れ替えても答えが変わらない。なのでソートしても問題ない。

これで $\displaystyle \sum_{i=1}^{N-1} \sum_{j=i+1}^{N}(A_j-A_i)$ を求めればよくなった。

このような「全ての2数ペアについて何らかの計算結果の総和を求める」系は、「1つの数が答えにどれだけ足されるか」というように個々の数に分解できないか考える。

すると、それぞれの数は「自分より大きい数とのペアでは $A_i$ 側になり負で作用する」「自分より小さい数とのペアでは $A_j$ 側になり正で作用する」ので、 自分より大きい数と小さい数の個数(=ソート順で何番目か)がわかれば個々に分解できる。

自分と等しい数がある場合がちょっと気になるが、気にせずソート順を基準にして扱えばちゃんと打ち消されるので問題ない。

Python3

E - Throne

E - Throne

問題

解法

N=12  S=3
   ○👑○
 ○      ○
○        S
 ○      ○
   ○○○

椅子の個数で移動を考える。

椅子の位置を考えると、$9,21,33,...,9+12k$ 個($k \ge 0$)移動すると玉座に座れる。
移動を考えると、$K,2K,3K,...$ 個移動した位置に座れる。

この2つが一致する非負の最小の数を求めれば、それを $K$ で割った値が答え。求めるのには中国剰余定理が使える。

Python3

ちゃんとライブラリ整備しとこうね。

検索して出てくるコードは「2つのmodが互いに素を前提としているか」「答えが求まらないときにエラーにならないか」が一定じゃなくて、どれ使えばいいかわからん問題が発生する。

上記のコードは AtCoder Library を移植して、inv_gcd() に相当する機能はPython3.8より使える負の指数の pow() に置き換えたもの。

解法2

公式解説の方法。

$kK \equiv -S \mod{N}$ と立式して、$\mod{N}$ における $K$ のモジュラ逆数 $K^{-1}$ を求め、$k \equiv -SK^{-1} \mod{N}$ とすれば求められる。

これもPythonなら負の指数の pow() を使えばここまで簡単になるんだね。

Python3

F - Rook on Grid

F - Rook on Grid

問題

解法

全てのマスについて考える $O(HW)$ は制約上無理。行ごと・列ごとに考えた結果を上手く集約する。

移動は↓→か→↓しかない。

□□□□□
□□■□□
□■□□□
□□□□□
□□■■□
□□□□□
■□■□□
□□□□■
□□□□□

まず、↓→を考える。
これは、各行で一番左に出てくる障害物まで取れる。1個も無い場合は全部取れる。その個数を整理する。

この内、1列目にある障害物はちょっと特殊で、障害物以降の行には行けない(回り込むには3手必要)ので、1回でも0が出てきて以降は全て0にする。

□□□□□  5
□□■□□  2
□■□□□  1
□□□□□  5
□□■■□  2
□□□□□  5
■□■□□  0
□□□□■  4 → 0
□□□□□  5 → 0

そんで、ここで求めた数字分は全部取れる。

↓→→→→  5
↓→■□□  2
↓■□□□  1
↓→→→→  5
↓→■■□  2
↓→→→→  5
■□■□□  0
□□□□■  0
□□□□□  0

あと取りたいのはここ(◇)。これは→↓で取る。今度は列についてさっきと同じことをしたいが、重複して数えないようにする必要がある。

  j  0 1 2 3 4  row
    ↓→→→→   5
    ↓→■◇◇   2
    ↓■  ◇◇   1
    ↓→→→→   5
    ↓→■■◇   2
    ↓→→→→   5
    ■  ■  ◇   0
            ■   0
                 0
col  6 2 1 4 7

ある列でたとえば $col[3]=4$ の時、「上から4行までの中で、$row$ の値が $3$ 未満」の行数が、新規に取れる◇となる。この場合は2個。

全体では $row$ の値が $3$ 未満の行はもっとあるのだが、$col[3]$ の処理時点では含まれないようにしたい。
そのためには、$col$ の値が小さい方から処理していくとよい。

幅 $W+1$ のFenwick木で、「今までで、$row$ の値が $x$ 未満の行が何個あったか」がすぐ取得できるように管理する。

Python3