−目次
パナソニックプログラミングコンテスト(AtCoder Beginner Contest 186)D,E,F問題メモ
パナソニックプログラミングコンテスト(AtCoder Beginner Contest 186)
中国剰余定理は何回見てもアルゴリズムが覚えられんのよね。
D - Sum of difference
問題
- N 個の整数 A1,...,AN が与えられる
- N−1∑i=1N∑j=i+1|Aj−Ai| を求めよ
- 2≤N≤2×105
- |Ai|≤108
解法
絶対値は「非負ならそのまま、負なら正負逆転」という操作と言い換えられ、「絶対に非負」という保証を作ってしまえば外せる。
そのためには、常に Aj≥Ai となればよくて、これは Ai を昇順にソートすれば保証される。
ソートしても、「A の全てのペアについて |Aj−Ai| を求めて合計する」という処理内容は一緒で、 |Aj−Ai| は、i と j を入れ替えても答えが変わらない。なのでソートしても問題ない。
これで N−1∑i=1N∑j=i+1(Aj−Ai) を求めればよくなった。
このような「全ての2数ペアについて何らかの計算結果の総和を求める」系は、「1つの数が答えにどれだけ足されるか」というように個々の数に分解できないか考える。
すると、それぞれの数は「自分より大きい数とのペアでは Ai 側になり負で作用する」「自分より小さい数とのペアでは Aj 側になり正で作用する」ので、 自分より大きい数と小さい数の個数(=ソート順で何番目か)がわかれば個々に分解できる。
自分と等しい数がある場合がちょっと気になるが、気にせずソート順を基準にして扱えばちゃんと打ち消されるので問題ない。
E - Throne
問題
- 円周上に N 個の椅子が並べられていて、1つは玉座
- 高橋君は最初、玉座から時計回りに数えて S 個隣の椅子に座っている
- 次の移動を繰り返して玉座に座りたい
- いま座っている椅子から時計回りに数えて K 個隣の椅子に移動する
- 高橋君が玉座に座ることができるか判定し、できる場合は最小の移動回数を求めよ
- T 個のテストケースに答えよ
- 2≤N≤109
- 1≤S<N
- 1≤K≤109
解法
N=12 S=3 ○👑○ ○ ○ ○ S ○ ○ ○○○
椅子の個数で移動を考える。
椅子の位置を考えると、9,21,33,...,9+12k 個(k≥0)移動すると玉座に座れる。
移動を考えると、K,2K,3K,... 個移動した位置に座れる。
この2つが一致する非負の最小の数を求めれば、それを K で割った値が答え。求めるのには中国剰余定理が使える。
- N で割ると N−S 余り、K で割ると 0 余る数を求めよ
ちゃんとライブラリ整備しとこうね。
検索して出てくるコードは「2つのmodが互いに素を前提としているか」「答えが求まらないときにエラーにならないか」が一定じゃなくて、どれ使えばいいかわからん問題が発生する。
上記のコードは AtCoder Library を移植して、inv_gcd()
に相当する機能はPython3.8より使える負の指数の pow()
に置き換えたもの。
解法2
公式解説の方法。
kK≡−SmodN と立式して、modN における K のモジュラ逆数 K−1 を求め、k≡−SK−1modN とすれば求められる。
これもPythonなら負の指数の pow()
を使えばここまで簡単になるんだね。
F - Rook on Grid
問題
- H×W の盤面の i 行目 j 列目を (i,j) とする
- 盤上にははじめ、(1,1) に将棋の飛車がいる
- 他に M 個の障害物がそれぞれ (Xi,Yi) にある
- 飛車が2回の移動で到達できるマスの個数を求めよ
- 1≤H,W≤2×105
- 0≤M≤2×105
解法
全てのマスについて考える 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 未満の行が何個あったか」がすぐ取得できるように管理する。