ABC 085
C - Otoshidama
問題
- 10000円札、5000円札、1000円札があわせて $N$ 枚あり、金額は $Y$ 円だという
- $N,Y$ の条件を満たす10000円札、5000円札、1000円札の枚数の組み合わせがあるか判定し、ある場合は一例を示せ
例
$N=1000, Y=1234000$ のとき、たとえば10000円札、5000円札、1000円札がそれぞれ14枚、27枚、959枚あれば、合計1000枚で
$$10000*14+5000*27+1000*959=1234000$$
となり、条件を満たす。また、26枚、0枚、974枚でも条件を満たす。どれを答えてもよい。
解法
O(N^2)
$1 \le N \le 2000$ なので、全部調べればいける
- 10000円札を0枚~N枚まで増やして調べる(x枚とする)
- 使える残り(y+z)は N-x 枚
- 5000円札を0枚~N-x枚まで増やして調べる(y枚とする)
- 1000円札の枚数が自動的に決まる(N-x-y 枚)
- これが $Y$ 円になるか調べる
# 擬似コード for x in 0 .. N: for y in 0 .. N-x: if 10000 * x + 5000 * y + 1000 * (N-x-y) == Y: # 発見! # ループ終了まで発見できない場合、NとYが不可能な組み合わせである
O(N)
鶴亀算の要領で差分を利用すると、計算量を減らせる
- とりあえず $N$ 枚全部1000円だったとすると、$1000N$ 円になる
- 実際の金額との差分を出す($diff = Y - 1000N$)
- この差分は、1000円札を1枚10000円札に替えると9000円、5000円札に替えると4000円減る
- つまり、$diff = 9000x + 4000y$
- ただし、$0 \le x, 0 \le y, x+y \le N$
あとは、この $x,y$ を1つずつ調べればよい。
def solve(n, y): tmp1000 = n * 1000 if tmp1000 > y: return -1, -1, -1 diff = y - tmp1000 for y in range(diff // 4000 + 1): tmp5000 = y * 4000 if diff - tmp5000 < 0: break if (diff - tmp5000) % 9000 == 0: x = (diff - tmp5000) // 9000 if x >= 0 and x + y <= n: return x, y, n - x - y return -1, -1, -1 print(*solve(*map(int, input().split())))
O(1)
連立方程式から考えると、$O(1)$ で解ける。解説放送参照。
$Y'=Y/1000$ として、
\begin{eqnarray} x+y+z &=& N \\ 10x+5y+z &=& Y' \end{eqnarray}
ここから、$x,y,z$ が非負整数という条件より、3つの条件式ができる。この条件式を満たす $z$ さえ見つければ、そこから求められる $x,y$ は必ず条件を満たしている。
$$ 0 \le z \le min(N,Y) \\ Y'-z は 5 の倍数 \\ N-z \le \frac{Y'-z}{5} \le 2(N-z) $$
一番下は、整理すると以下になる。
$$\frac{5N-Y'}{4} \le z \le \frac{10N-Y'}{9}$$
たとえば、$N=1000,Y=1234000$ なら、
$$ 0 \le z \le min(1000, 1234) = 1000 \\ z \equiv 4 \mod 5 \\ \frac{5000-1234}{4} = 941.5 \le z \le 974 = \frac{10000-1234}{9} $$
となり、$z=944,949,954, ...$ で解を持つことが分かる。
ただ、計算量的に十分間に合うなら、競技プログラミングでは考察・実装にかかる時間が短い方を選択するべきなので、最初の方法でいい。
D - Katana Thrower
問題
- 刀が $N$ 本あり、ヒットポイント $H$ の1匹の魔物を倒す
- $i$ 番目の刀は、切りつけると $a_i$、投げると $b_i$ のダメージを与えられる($1 \le a_i \le b_i$)
- 切るのは何度でもできるが、投げると刀は無くなり、それ以上切ることも投げることも出来なくなる
- 倒すのに必要な最小手数(切る回数+投げる回数)はいくつか
解法
計算問題だが、考えるべき条件がいくつかある。
- 攻撃の順番は自由なので、切るだけ切ってから投げるとよい
- 切る刀は、一番攻撃力($a_i$)の高い刀だけ使えばよい($a_{max}$ とする)
- 投げる刀は、$a_{max}$ より攻撃力($b_i$)が高い刀を投げる
- $b_i$ が $a_{max}$ 以下の刀は、投げずにその分 $a_{max}$ の刀で切った方がよい
とりあえず $b_i$ が高い刀から順に投げて、倒せたらそれまでに投げた本数が答え。
投げるだけでは倒せない場合、切ってダメージを与える。投げた後の残りHPを $R$ とすると、$ceil(R / a_{max})$ で、切る回数が求められる。
from math import ceil def solve(h, aa, bb): max_a = max(aa) i = 0 for b in sorted(bb, reverse=True): if b <= max_a: break i += 1 h -= b if h <= 0: return i return i + ceil(h / max_a) n, h = map(int, input().split()) aa, bb = [], [] for _ in range(n): a, b = map(int, input().split()) aa.append(a) bb.append(b) print(solve(h, aa, bb))