Loading [MathJax]/jax/output/CommonHTML/jax.js

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

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

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

D - Sum of difference

問題

  • N 個の整数 A1,...,AN が与えられる
  • N1i=1Nj=i+1|AjAi| を求めよ
  • 2N2×105
  • |Ai|108

解法

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

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

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

これで N1i=1Nj=i+1(AjAi) を求めればよくなった。

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

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

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

Python3

E - Throne

問題

  • 円周上に N 個の椅子が並べられていて、1つは玉座
  • 高橋君は最初、玉座から時計回りに数えて S 個隣の椅子に座っている
  • 次の移動を繰り返して玉座に座りたい
    • いま座っている椅子から時計回りに数えて K 個隣の椅子に移動する
  • 高橋君が玉座に座ることができるか判定し、できる場合は最小の移動回数を求めよ
  • T 個のテストケースに答えよ
  • 2N109
  • 1S<N
  • 1K109

解法

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

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

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

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

  • N で割ると NS 余り、K で割ると 0 余る数を求めよ

Python3

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

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

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

解法2

公式解説の方法。

kKSmodN と立式して、modN における K のモジュラ逆数 K1 を求め、kSK1modN とすれば求められる。

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

Python3

F - Rook on Grid

問題

  • H×W の盤面の i 行目 j 列目を (i,j) とする
  • 盤上にははじめ、(1,1) に将棋の飛車がいる
  • 他に M 個の障害物がそれぞれ (Xi,Yi) にある
  • 飛車が2回の移動で到達できるマスの個数を求めよ
  • 1H,W2×105
  • 0M2×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 未満の行が何個あったか」がすぐ取得できるように管理する。

Python3

programming_algorithm/contest_history/atcoder/2020/1219_abc186.txt · 最終更新: 2020/12/23 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0