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

AtCoder Beginner Contest 182 C,D,E,F問題メモ

C - To 3

問題

  • 各桁に0が含まれない正整数 N が与えられる
  • (10進法で)N の桁をいくつか消して残りをそのままの順序で結合する操作を考える
  • 操作によって3の倍数に出来るか判定し、出来る場合は消す桁の最小個数を求めよ
  • 1N1018

解法

N が大きい場合は各桁の和が3の倍数となることを利用する方法が速いが、 今回はたかだか18桁しかないので、実際に全部試してもよい。

Pythonなら itertools.combinations(リスト的なもの, k) で、 リスト的なものから k 個選ばれる全パターンのイテレータを得られる。
並び順も元のリスト的なものの状態が保たれている。

Python3

D - Wandering

解法

累積和と累積MAXを同時に使う面白い問題。

i 回目の「操作」を「A1 進み、A2 進み、…、Ai 進む」行為とし、j 回目の「移動」をその操作中での「Aj 進む」行為のことを指すとする。

Ci: i 回目の操作の前と後でどれだけ(正の向きに)進むか」は、A1+A2+...+Ai なので各 i についての値を累積和で求められる。

また、i 回目の操作の移動中に「Di: 初期位置から最大いくつ進んだ座標まで訪れることがあるか」というのは、

  • max(A1,A1+A2,A1+A2+A3,...,A1+A2+...+Ai)

なので、“累積和の累積MAX”で求められる。

「今まで進んだことのある最大座標 p」「操作後の座標 q」を持ち、各操作毎に、

  • 今回の操作中に訪れる最大座標が p より大きければ更新
    • pq+Di
  • 操作後の座標を更新
    • q=q+Ci

として p,q を更新していくと、p が答えとなる。

Python3

E - Akari

問題

  • H×W のマス目
  • N 個の電球と M 個のブロックが置かれている
    • i 個目の電球はマス (Ai,Bi)
    • i 個目のブロックはマス (Ci,Di)
    • 1つのマスに置かれている電球とブロックはたかだか1個
  • 電球は、ブロックに阻まれるまで、上下左右の 4 方向に光を放つ
  • マス目上のブロックの置かれていないマスのうち、電球の光が届くものの数を求めよ
  • 1H,W1500
  • 1N5×105
  • 1M105

解法

Eにしては、素直に調べるだけ、という印象。
一応、上下左右に照らされるマスを調べる過程で、それ以上調べる必要の無い条件をはっきりさせて打ち切らないとTLEになる、という工夫は必要。

○:照明  ■:壁  ─:光線

──①───②───■

このような時、各照明(①)から上下左右に照らされるマスを1マスずつ調べるが、 たとえば右方向に調べて他の照明(②)にぶつかったら、それよりさらに右は②を調べる過程で調べられるので、①からはこれ以上調べる必要は無い。

そうすることで同じマスを何度も調べること無く、H×W の各マスを、同じ方向ではせいぜい1回だけ調べればよくなる(全体で4方向なのでせいぜい 4HW)。

また、制約を見ると照明より壁の方が数が少ないので、壁から調べることもできる。

外周を含めた各壁から同様に上下左右に調べて、他の壁または照明にぶつかったら打ち切る。
ここで探索されたマスは、たとえば左方向への探索では、「右から照らされることは無いマス」と言える。

・・○・・■・・・■・・
      ↑↑  ↑↑↑
      右方向から照らされることは無いマス

上下左右どの方向からも照らされることは無いマスが最終的に照らされないマスとなるので、全体から引けばよい。

Python3

F - Valid payments

問題

  • A1 円玉、A2 円玉、…、AN 円玉の N 種類のコインがある
    • A1=1
    • i について Ai<Ai+1 かつ Ai+1Ai の倍数
  • X 円の商品を購入する
  • y 円払うと yX 円のお釣りが返る
  • 以下の条件を全て満たすような支払額 y が何通りあるか求めよ
    • 支払いもお釣りも、その金額をちょうど払うのに必要な最小の枚数で受け渡しを行った
    • 支払ったコインと同じ種類のコインがお釣りに含まれてはいけない
  • 1N50
  • 1=A1<A2<...<AN1015
  • 1X1015

解法

日常でも、なんかボケてると必要のない硬貨を渡して店員にそのまま返されることってたまにある。

ここでは、「お釣りに含まれない一番大きいコイン」で場合分けした。

入出力サンプルからいろいろ条件を探しての解法だが、公式解説にあるような解法2の方がスマート。

前処理

まず、サンプル3のように、XAi2i)の倍数なら、Ai 未満のコインは使われない。
yyXmodAi なので、もしこれが0でなかった場合、支払いとお釣りに同じ硬貨が含まれてしまう。

かならず最小の硬貨が使われるよう、全体を上記を満たす最大の Ai で割り、それ未満のコインはなかったものとする。

N=5  X=44  A=(1, 2, 4, 20, 100)
↓
N=3  X=11  A=(      1,  5,  25)

これで、置き換え後の A1=1 は、ぴったり払う上で必ず使われることが保証される。

この置き換えは後続の計算を上手くやると不必要かもだが、思考する上で条件の見落としが減ってわかりやすくなる。

サンプル観察

サンプル2では、1,5,10,50,100のコインで198円を支払う。

       
       支払いに含まれる   お釣りに含まれるコイン
198    1,5,10,50,100      -
200              100      1
203    1,        100        5
208    1,5,      100          10
248    1,5,10,   100             50

203~248は、200円をベースとして、お釣りに含まれるコインを支払い側に順次含ませることで、お釣り側を繰り上げている感じ。

すると、細かな詰めの部分はあるとしても以下の方針がとれそう。

  • 支払う最小のコインを Ai とし、ギリギリ X を超える支払額 yi、その時のお釣り zi を求める
    • yi=ceil(XAi)×Ai
    • お釣りには、A1Ai1 のコインのみが含まれる
  • お釣り側のどのコインを支払い側に含ませて、お釣りを繰り上げるかの派生パターン数を求める
  • ただし、お釣りには Ai(以上)が含まれないようにする
  • これを各 Ai について合計する

Ai1 を繰り上げると Ai がお釣りに含まれてしまうため、繰り上げ可能なのは Ai2 まで。

これで求められる派生を含めた y の範囲は、お釣りに Ai 以上が含まれないことから「yi 以上 X+Ai 未満」となる。

yi<yi+1 であれば、X+Aiyi+Aiyi+1 のため、Ai+1 について求める場合の範囲「yi+1 以上 X+Ai+1 未満」とは被らない。

一方、yi=yi+1 の場合は範囲が丸かぶりするが、yi+1 の方がカバー範囲が広いため、そちらでまとめて計算することとする。

派生パターンの求め方

場合分けの方針は良さそうなので、派生パターンを数える。

ざっくり考えると、Ai の時には、A1Ai2 をそれぞれ繰り上げるか否かの 2i2 通りくらいあるかな?と思う。
しかし、サンプル4は、コイン枚数の割には支払い方が21種類と意外と少ない。
数字の大きさにびびらず「何枚で次のコインに繰り上がるか」「最大コインだけで支払った場合のお釣り」を観察してみる。

X = 11837029798
                 1 942454037 2827362111 19791534777 257289952101 771869856303 3859349281515 30874794252120 216123559764840
繰り上げ
枚数     942454037         3          7          13            3            5             8              7

お釣り   414872683         2          2          12            2            4             7              6

すると、A4 以降は全て繰り上げ枚数がお釣りギリギリとなっている。

つまり、A3 などを支払い側に含ませてお釣りを繰り上げようとすると、連鎖的に繰り上がって最大の A9 が含まれることになる。
A9 については、A8 のみならず A3A7 も全て繰り上げ不可となる。

A2 もギリギリのため、結局、派生は以下の2通りしかないことになる。

  • A1 を支払いに含めて繰り上げる。お釣りでは A2 も繰り上がるため A3 が1枚増える
  • A2 を支払いに含めて繰り上げる。お釣りでは A3 が1枚増える

また、お釣りが0枚のコインも、繰り上げられない。(繰り上げ枚数の分だけコインを追加することになり、それなら Ai+1 1枚で払うことになる)
ただし、下の桁から繰り上がってきた1枚があれば、追加するのは(繰上げ枚数-1)枚となり、繰り上げられる。

このように、下の桁からの繰り上げの有無で遷移が変わるのは、桁DPを想起させる。

  • DP[i][j:0/1]= 小さい方から Ai までで、j=0/1:上の桁への繰り上がり無し/ありの場合のパターン数

とすると、お釣りが「0枚」「繰り上げ枚数ギリギリ」「それ以外」で遷移が変化する。

お釣り
       0       繰り上げギリギリ    それ以外
     i   i+1      i   i+1          i   i+1
j=0  o → o       o → o           o → o
       ↗           ↘               ×
j=1  o → o       o → o           o → o

最終的に Ai まで繰り上がってくるパターンが不可なので、繰り上がりのない DP[i1][0]Ai に対しての派生パターン数となる。

まとめると、

  • 前処理する
  • Ai 以上のお釣りを含まない最小の金額 yi を求める
  • お釣り yiX の構成を求める
  • DP[0][0]=1,DP[0][1]=0 からはじめて派生パターン数を求める

これを合計すればよい。ただし、yi=yi+1 となる i については合計しない。

Python3

解法2

要は、「C1A1+C2A2+...+CNAN=X となる整数の組 (C1,C2,...,CN) は何通りですか」という問題で、 Ci が正ならその枚数だけ支払い、負ならお釣りに用いると考えると問題のシチュエーションに当てはめられる。

ただし各 Ci の絶対値には上限があり、 1段階大きいコインで表現できてしまう枚数(Ai+1Ai)以上は使うことが出来ない。

この上限制約があるため、A1,A2,...,Ai1 の全てを上限まで使っても Ai 未満にしかならない。

また、以下のようにどちらからどちらの変換へも1通りに決まるので、Ci の組と y は1対1対応とわかる。

  • y を決めれば支払いとお釣りは1通りに決まる
  • (C1,C2,...,CN) を決めれば当然、y は1通りに決まる

以上を踏まえて Cii の大きい方から考えると、各コイン、支払う可能性のある枚数というのは、

  • X をちょうど払える場合、1通り
  • X をちょうど払えない場合、以下の2通り
    • X にギリギリ足りない(あと+1すれば超える)
    • X をギリギリ超える(あと-1すれば足りなくなる)
    • ただし(絶対値で)上限を超える枚数は不可

で、ちょうど払えない場合は端数を1段階小さいコインで表現する方法を考えれば、再帰的に解ける。

もしこれ以外の枚数だと、端数の絶対値が Ai 以上ということになり、A1Ai1 では表現できないため、不可能。

一見、2通りずつに分岐するので状態数が 2N とかになってしまいそうだが、 実は以下のように多くの状態が同じになり、メモ化すれば 2N 程度にしかならない。

  • X=198 を100円で
    • ギリギリ足りない: 1枚→X=98
    • ギリギリ超える: 2枚→X=2
  • X=98 を50円で
    • ギリギリ足りない: 1枚→X=48
    • ギリギリ超える: 2枚→支払いが上限を超えるため不可
  • X=2 を50円で
    • ギリギリ足りない: -1枚→X=48
    • ギリギリ超える: 0枚→X=2

(100×1, 50×1 の支払い)と、(100×2 の支払い, 50×1のお釣り)とで、端数48が同じになる。

Python3

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