AtCoder Beginner Contest 210をPythonで解く

投稿日:  更新日:2022/09/02

Atcoder Python

B!
Daniel AgreloによるPixabay(https://pixabay.com/)からの画像

こんにちは, Shinoryoです.

今回はAtCoder Beginner Contest 210を, Pythonで解いてみた結果を書き連ねていこうと思います.

A - Cabbages

NAの場合, 全てのキャベツが1X円で購入できるので, 合計金額はNX円です.

N>Aの場合, A個のキャベツが1X円で購入出来て, NA個のキャベツが1個Y円で購入できるので, 合計金額はAX+(NA)Y円です.

以上から, どちらの場合も合計金額をmin(N,A)×X+max(0,NA)×Y円と書けます.

# 入力
N, A, X, Y = [int(x) for x in input().split()]
# 出力
print(min(N, A) * X + max(0, N - A) * Y)

B - Bouzu Mekuri

文字列Sの先頭から見て初めて1が現れる場所をX文字目とします(ただし, 先頭から0文字目, 1文字目と数えることとし, これはインデックスにそのまま一致します). Xが偶数ならばそのカードを高橋君が食べ, Xが奇数ならばそのカードを青木君が食べることになります.

文字列Sにある文字が含まれるかどうかを探索するには, .find()を使用することができます. .find()を使う場合, 先頭から探索します.

# 入力
N = int(input())
S = input()
# 最初に1が出てくる位置を検索
firstone = S.find("1")
# firstoneが偶数ならTakahashi、奇数ならAoki
if firstone % 2 == 0:
print("Takahashi")
else:
print("Aoki")

.find()を使わなくても, 明示的に探索の操作を行えばよいです.

# 入力
N = int(input())
S = input()
# 最初に1が出てくる位置を検索
for i in range(N):
if S[i] == "1":
firstone = i
break
# firstoneが偶数ならTakahashi、奇数ならAoki
if firstone % 2 == 0:
print("Takahashi")
else:
print("Aoki")

C - Colorful Candies

1つの区間に含まれるキャンディの色の種類数を求めるには 「区間内のK個のキャンディを一つずつ見て, 区間内の色の種類数を数える」という方法が考えられます. しかし, これだと時間計算量にO(K(NK+1))になり, TLEです.

from collections import deque
# 入力
N, K = [int(x) for x in input().split()]
c = [int(x) for x in input().split()]
# cから切り出したdeque
cslice = deque(c[0:K])
# 答えを格納する変数
ans = len(set(cslice))
# キャンディをもらう全通り
for i in range(N - K):
# 左端とこれから加えようとするものが同じなら
if c[i + K] == cslice[0]:
# 更新の操作だけする
cslice.popleft()
cslice.append(c[i + K])
# そうでなければ
else:
# 更新の操作に加えて種類の最高記録を更新
cslice.popleft()
cslice.append(c[i + K])
ans = max(ans, len(set(cslice)))
# 出力
print(ans)

そこで, 「各i=1,2,,NKに対する[i,i+K1][i+1,i+K]のキャンディの純粋に差分を確認する」という方法を考えます. 辞書型を用いて{1:number(1),2:number(2),3:number(3),}のように各キャンディの個数を記録し, それを変化させていく方法を考えます. また, キャンディの種類数を別に記録しておけば, 辞書型のvalueを合計する時間計算量を減らすことができます.

# 入力
N, K = [int(x) for x in input().split()]
c = [int(x) for x in input().split()]
# 連想配列を作る
# Pythonには辞書型という嬉しいものがある
c_dict = {x : 0 for x in set(c)}
# 最初の一歩
# index 0からk-1までのcを見て、その情報をc_dictに記録
for i in range(K):
c_dict[c[i]] += 1
# index 0からk-1までを見たときの種類数を調査
temp = sum(c_dict[i] != 0 for i in c_dict)
# 答えを格納する変数
ans = temp
# これ以降は調査する場所を1つずつずらしていく
for i in range(1, N-K+1):
# 先頭のものを削除
c_dict[c[i-1]] -= 1
# 削除した結果0になったら、種類数が1減る
if c_dict[c[i-1]] == 0:
temp -= 1
# 後ろに新しいものを追加
c_dict[c[i+K-1]] += 1
# 追加した結果1になったら、種類数が1増える
if c_dict[c[i+K-1]] == 1:
temp += 1
# ansを更新
ans = max(ans, temp)
# 出力
print(ans)

D - National Railway

この問題は駅(is,js)から駅(if,jf)までの鉄道を敷設するのにかかる最小の値段を動的計画法(dynamic programming)を用いて考えていきます.

その際, is<=ifは一般に仮定してよいことに注意してください. 駅(is,js)から駅(if,jf)までの鉄道を敷設するのにかかる最小の値段は, 駅(if,jf)から駅(is,js)までの鉄道を敷設するのにかかる最小の値段と同一であることから, そのように仮定してよいことが分かります.

また, js<=jfも仮定します. js>=jfのパターンは, 例えば行列Ai,jの左右を反転させることによって実現できます. (これに関しては, Ai,jの左右を反転させたものに対して同様の作業を繰り返さなければならないことに注意してください. )

中間地点(i,j)を境に2段階に分けて考えると楽になります.

  • 青木君が一方の駅をすでに建設して現在(i,j)にいるとき, それまでにかかった費用として考えられる最小値(dp[i][j])
    1. (i,j)に駅を立てた
    2. (i1,j)から(i,j)に線路を引いて移動してきた
    3. (i,j1)から(i,j)に線路を引いて移動してきた
  • 2つ目の駅を(i,j)に建てて青木君がどこかへ飛び立ったとき, そこまでにかかった費用として考えられる最小値(X[i][j])
    1. (i1,j)から(i,j)に線路を引いて移動してきて, (i,j)に駅を立てた
    2. (i,j1)から(i,j)に線路を引いて移動してきて, (i,j)に駅を立てた

Ai,jの左右反転には, 例えば.reverse()を用いると便利です.

多次元配列の最小値を求めるには, 例えば多次元リストの平坦化を用いると便利です. Pythonには, itertoolsモジュールのitertools.chain.from_iterable()があり, itertools.chain.from_iterable(matrix)のような形で, 2次元配列を1次元配列にすることができます.

なお, Python (3.8.2)で実行するとTLEになります. PyPy3 (7.3.0)で実行することでACです.

import itertools
# 入力
H, W, C = [int(x) for x in input().split()]
A_matrix = [[int(x) for x in input().split()] for _ in range(H)]
INF = float('inf')
# (is, js) → (if, jf)
# is <= if は一般に仮定してよい
# まずは、js <= jf とする
# dp[i][j]: 青木君が一方の駅をすでに建設して、現在(i,j)にいるとき
# それまでにかかった費用として考えられる最小値
# 考えられるパターン
# 1) (i, j)に駅を立てた
# 2) (i-1, j)から(i,j)に線路を引いて移動してきた
# 3) (i, j-1)から(i,j)に線路を引いて移動してきた
dp = [[INF for _ in range(W)] for _ in range(H)]
for i in range(H):
for j in range(W):
if i == 0 and j == 0:
dp[i][j] = A_matrix[i][j]
elif i == 0:
dp[i][j] = min(A_matrix[i][j], dp[i][j-1] + C)
elif j == 0:
dp[i][j] = min(A_matrix[i][j], dp[i-1][j] + C)
else:
dp[i][j] = min(A_matrix[i][j], dp[i][j-1] + C, dp[i-1][j] + C)
# X[i][j]: 2つ目の駅を(i, j)に建てて青木君がどこかへ飛び立ったとき
# そこまでにかかった費用として考えられる最小値
# 考えられるパターン
# 2) (i-1, j)から(i,j)に線路を引いて移動してきて、(i,j)に駅を立てた
# 3) (i, j-1)から(i,j)に線路を引いて移動してきて、(i,j)に駅を立てた
X = [[INF for _ in range(W)] for _ in range(H)]
for i in range(H):
for j in range(W):
if i == 0 and j == 0:
continue
elif i == 0:
X[i][j] = dp[i][j-1] + C + A_matrix[i][j]
elif j == 0:
X[i][j] = dp[i-1][j] + C + A_matrix[i][j]
else:
X[i][j] = min(dp[i][j-1] + C + A_matrix[i][j], dp[i-1][j] + C + A_matrix[i][j])
# Xの最小値を格納
ans1 = min(itertools.chain.from_iterable(X))
# 次には、js >= jf とする
# そのためには、A_matrixを左右反転させればよい
for i in range(H):
A_matrix[i].reverse()
# dp[i][j]: 青木君が一方の駅をすでに建設して、現在(i,j)にいるとき
# それまでにかかった費用として考えられる最小値
# 考えられるパターン
# 1) (i, j)に駅を立てた
# 2) (i-1, j)から(i,j)に線路を引いて移動してきた
# 3) (i, j-1)から(i,j)に線路を引いて移動してきた
dp = [[INF for _ in range(W)] for _ in range(H)]
for i in range(H):
for j in range(W):
if i == 0 and j == 0:
dp[i][j] = A_matrix[i][j]
elif i == 0:
dp[i][j] = min(A_matrix[i][j], dp[i][j-1] + C)
elif j == 0:
dp[i][j] = min(A_matrix[i][j], dp[i-1][j] + C)
else:
dp[i][j] = min(A_matrix[i][j], dp[i][j-1] + C, dp[i-1][j] + C)
# X[i][j]: 2つ目の駅を(i, j)に建てて青木君がどこかへ飛び立ったとき
# そこまでにかかった費用として考えられる最小値
# 考えられるパターン
# 2) (i-1, j)から(i,j)に線路を引いて移動してきて、(i,j)に駅を立てた
# 3) (i, j-1)から(i,j)に線路を引いて移動してきて、(i,j)に駅を立てた
X = [[INF for _ in range(W)] for _ in range(H)]
for i in range(H):
for j in range(W):
if i == 0 and j == 0:
continue
elif i == 0:
X[i][j] = dp[i][j-1] + C + A_matrix[i][j]
elif j == 0:
X[i][j] = dp[i-1][j] + C + A_matrix[i][j]
else:
X[i][j] = min(dp[i][j-1] + C + A_matrix[i][j], dp[i-1][j] + C + A_matrix[i][j])
# Xの最小値を格納
ans2 = min(itertools.chain.from_iterable(X))
# 出力
print(min(ans1, ans2))

E以降

E以降は私の能力不足故に省略いたします.

参考にしたサイト等