東京海上日動プログラミングコンテスト2023(AtCoder Beginner Contest 304)をPythonで解く

投稿日:  更新日:2023/06/17

Atcoder Python

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

こんにちは, Shinoryoです.

今回は東京海上日動プログラミングコンテスト2023(AtCoder Beginner Contest 304)を, Pythonで解いてみた結果を書き連ねていこうと思います.

A - First Player

Aの最小値とそのインデックス(A_min_index)を取得し, A_min_index以降の要素を出力した後, A_min_indexより前の要素を出力すればよいです.

# 入力
N = int(input())
S = []
A = []
for _ in range(N):
S_i, A_i = input().split()
S.append(S_i)
A.append(int(A_i))
# Aの最小値とそのインデックスを取得
A_min = min(A)
A_min_index = A.index(A_min)
# 出力
# A_min_index以降の要素を出力
for i in range(A_min_index, N):
print(S[i])
# A_min_indexより前の要素を出力
for i in range(A_min_index):
print(S[i])

B - Subscribers

Nを文字列まま入力を受け付けて, 4文字目以降をすべて0に置き換えればよいです.

# 入力
N = list(input())
# 文字列のまま切り捨ての操作
for i in range(len(N) - 3):
N[- (i + 1)] = "0"
# 出力
print("".join(N))

C - Virus

下の図のように, 人の間の距離がD以内の人同士をくっつけていくことを考えます.

このようにくっついた人同士の集合というのを考えることができます.

もし1の人が感染したら, 最終的には1の人が含まれる集合に属する人は感染しますが, それ以外の人には感染しないことになります.

このような操作は例えば, Union Findを用いて実装することができます. 最初は1つひとつバラバラな要素(人)が用意されます. そして, 「2つのまとまり(人)をつなげる」という操作を繰り返し, 最終的にある要素がどの集合に属しているかを管理していくアルゴリズムです.

「2つのまとまり(人)をつなげる」という操作に時間計算量がO(N2)かかります. Python3.8だとTLEですが, PyPy3だとACです.

以下の例では, nkmk様による「PythonでのUnion-Find(素集合データ構造)の実装と使い方」(https://note.nkmk.me/python-union-find/)に掲載されているUnion Findのコードを使用しています.

from itertools import combinations
# Union Find
# https://note.nkmk.me/python-union-find/
class UnionFind():
def __init__(self, n):
self.n = n
self.parents = [-1] * n
def find(self, x):
if self.parents[x] < 0:
return x
else:
self.parents[x] = self.find(self.parents[x])
return self.parents[x]
def union(self, x, y):
x = self.find(x)
y = self.find(y)
if x == y:
return
if self.parents[x] > self.parents[y]:
x, y = y, x
self.parents[x] += self.parents[y]
self.parents[y] = x
# 入力
N, D = list(map(int, input().split()))
position = [list(map(int, input().split())) for _ in range(N)]
# Union Find生成
infection_connection = UnionFind(N)
# 全ての2人組の組み合わせでループし、その間の距離D以下なら、その2つをUnionFind.union()でくっつける
for i, j in combinations(range(N), 2):
if (position[i][0] - position[j][0]) * (position[i][0] - position[j][0]) + (position[i][1] - position[j][1]) * (position[i][1] - position[j][1]) <= D * D:
infection_connection.union(i, j)
# index 0の人が属する集合の、Union Find上のrootを取得
root = infection_connection.find(0)
# Union Find上のrootが一致している人は、同じ集合に属していて感染していることになる
for i in range(N):
if infection_connection.find(i) == root:
print("Yes")
else:
print("No")

Union Findは, 以下のAtCoder Beginner Contestでも登場しています.

D - A Piece of Cake

左からX列目, 下からY行目にあるピースのIDを(X,Y)と定めます(X,Yの取りうる数には0を含みます).

ある苺の座標を(p,q), ケーキを通るy軸に並行な異なる直線の座標を(a1,a2,,aA,W), ケーキを通るx軸に並行な異なる直線の座標を(b1,b2,,bB,H)とします(便宜上, W, Hもくっつけておきます). すると, その苺が乗っているピースのIDのXの方は, (a1,a2,,aA,W)の中にpを挿入する位置のインデックスとして得ることができます. 同様に, その苺が乗っているピースのIDのYの方は, (b1,b2,,bB,H)の中にqを挿入する位置のインデックスとして得ることができます.

その位置の情報をもとに, (ピースのID,そこに乗っている苺の数)のタプルを, 苺の数の多い順に出力することができます. mに関しては, 以下の2パターンが考えられます.

  • 苺の乗っているピースが(A+1)×(B+1)(ピースの総数)よりも少ない場合
    m=0となります.
  • 苺の乗っているピースが(A+1)×(B+1)(ピースの総数)と等しい場合
    mとしては先ほどのタプルのリストの最も後ろ側を参照します.

Mに関しては, 先ほどのタプルのリストの最も前側を参照します.

二分探索にはbisectモジュールを, (ピースのID,そこに乗っている苺の数)のタプルを苺の数の多い順に出力するのにはCollectionsモジュールを使用することができます.

from bisect import bisect_left
from collections import Counter
# 入力
W, H = map(int, input().split())
N = int(input())
strawberries = [list(map(int, input().split())) for _ in range(N)]
A = int(input())
a_list = list(map(int, input().split()))
a_list.append(W)
B = int(input())
b_list = list(map(int, input().split()))
b_list.append(H)
# 左からX列目、下からY行目にあるピースのIDを(X,Y)と定める
# 苺が乗っているピースのIDを二分探索で調査
strawberries_location = [bisect_left(a_list, strawberries[i][0]) + bisect_left(b_list, strawberries[i][1]) * (10**6) for i in range(N)]
# (ピースのID,そこに乗っている苺の数)のタプルを、苺の数の多い順に出力
strawberries_count = Counter(strawberries_location).most_common()
# mを求める
if len(strawberries_count) < (A + 1) * (B + 1):
m = 0
else:
m = strawberries_count[-1][1]
# Mを求める
M = strawberries_count[0][1]
# 出力
print(m, M)

E以降

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

参考にしたサイト等