AtCoder Beginner Contest 194をPythonで解く

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

Atcoder Python

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

こんにちは, Shinoryoです.

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

A - I Scream

条件を書いてif文で分岐させれば問題ないです.

# 入力
A, B = [int(x) for x in input().split()]
# 出力
if A + B >= 15 and B >= 8:
print(1)
elif A + B >= 10 and B >= 3:
print(2)
elif A + B >= 3:
print(3)
else:
print(4)

B - Job Assignment

考え方はいくつかあります.

計算にちょっと時間がかかる方法

まずは,

  • 1人でどちらの仕事もやるパターン
  • 2人でどちらの仕事もやるパターン

の全場合を探索し, 最小値を出す方法です. 1人でどちらの仕事もやるパターンを全て調べるのにO(N), 2人でどちらの仕事もやるパターンを全て調べるのにO(N2)かかるので, この方法だと最終的にO(N2)だけかかります.

import itertools
# 入力
N = int(input())
A = []
B = []
for _ in range(N):
A_i, B_i = [int(x) for x in input().split()]
A.append(A_i)
B.append(B_i)
# 答えを格納する変数
# 10^6はかからないと踏んで
ans = 10**6
# 1人で行う場合を探索
for i in range(N):
ans = min(ans, A[i] + B[i])
# 2人で行う場合を探索
for i,j in itertools.permutations(range(N),2):
ans = min(ans, max(A[i], B[j]))
# 出力
print(ans)

計算にそこまで時間がかからない方法

2人でどちらの仕事もやるパターンを全探索しないでやることもできますね.

  • 1人でどちらの仕事もやるパターン
  • Aの仕事に最も時間がかからない人との組み合わせのパターン
  • Bの仕事に最も時間がかからない人との組み合わせのパターン

の全場合を探索し, 最小値を出す方法です. 上記すべてのパターンを全て調べるのにO(N)かかるので, この方法だと最終的にO(N)だけかかります.

# 入力
N = int(input())
A = []
B = []
for _ in range(N):
A_i, B_i = [int(x) for x in input().split()]
A.append(A_i)
B.append(B_i)
# 答えを格納する変数
# 10^6はかからないと踏んで
ans = 10**6
# 1人で行う場合を探索
for i in range(N):
ans = min(ans, A[i] + B[i])
# 2人で行う場合を探索
# Aのかかる時間が最も短い人を採用した場合
# Aのかかる最も短い時間を格納
A_min = 10**6
# Aのかかる時間が最も短い人リストを格納
A_min_number = []
# A_min、A_min_numberを調査
for i in range(N):
if A_min > A[i]:
A_min = A[i]
A_min_number = [i]
elif A_min == A[i]:
A_min_number.append(i)
# A_min_numberの要素数(時間最低タイが存在しないか)で場合分け
if len(A_min_number) > 1:
# 時間最低タイが存在すれば、とりあえずmin(B)でOK
ans = min(ans, max(A_min, min(B)))
else:
# 時間最低タイが存在しなければ、インデックスA_min_number[0]を除いたB_tempを用意しmin(B_temp)
ans = min(ans, max(A_min, min(B[:A_min_number[0]] + B[A_min_number[0] + 1:])))
# Bのかかる時間が最も短い人を採用した場合
# Bのかかる最も短い時間を格納
B_min = 10**6
# Bのかかる時間が最も短い人リストを格納
B_min_number = []
# B_min、B_min_numberを調査
for i in range(N):
if B_min > B[i]:
B_min = B[i]
B_min_number = [i]
elif B_min == B[i]:
B_min_number.append(i)
# B_min_numberの要素数(時間最低タイが存在しないか)で場合分け
if len(B_min_number) > 1:
# 時間最低タイが存在すれば、とりあえずmin(A)でOK
ans = min(ans, max(B_min, min(A)))
else:
# 時間最低タイが存在しなければ、インデックスB_min_number[0]を除いたA_tempを用意しmin(A_temp)
ans = min(ans, max(B_min, min(A[:B_min_number[0]] + A[B_min_number[0] + 1:])))
# 出力
print(ans)

上で書いたようなものをよりスマートに書き直したようなものが, AtCoderの解説に載っていました.

C - Squared Error

問題文にあることを律儀に実行すると, O(N2)だけかかり, TLEです.

# 入力
N = int(input())
A = [int(x) for x in input().split()]
# 答えを格納する変数
ans = 0
# 律儀に加えていく
for i in range(1,N):
for j in range(i):
ans += (A[i] - A[j])**2
# 出力
print(ans)

そこで, 例えば次のように考えます.

i=2Nj=1i1(AiAj)2=i=2Nj=1i1(Ai22AiAj+Aj2)(1)=i=2N(i1)Ai22i=2N(Aij=1i1Aj)+i=2Nj=1i1Aj2

それぞれ累積和や二乗の累積和はO(N)で事前に求めておくことができます. よって, このように展開するだけでO(N)で解くことができます.

# 入力
N = int(input())
A = [int(x) for x in input().split()]
# Aの累積和を格納
Aruiseki = []
temp = 0
for i in range(N):
temp += A[i]
Aruiseki.append(temp)
# Aの2乗の累積和を格納
Asquaredruiseki = []
temp = 0
for i in range(N):
temp += A[i]**2
Asquaredruiseki.append(temp)
# 答えを格納する変数
ans = 0
# 計算
for i in range(1,N):
ans += i * (A[i]**2)
ans -= 2 * A[i] * Aruiseki[i-1]
ans += Asquaredruiseki[i-1]
# 出力
print(ans)

あるいは, 次のようにも考えることができます. の上下限は, 差の二乗をとる際に同じ組み合わせが現れないようにしてあります. AiAi=0ですので, ijも両方1からNまで足し上げて1/2してもいいんですね.

i=2Nj=1i1(AiAj)2=12i=1Nj=1N(AiAj)2=12i=1Nj=1N(Ai22AiAj+Aj2)=12(Ni=1NAi22i=1N(Aij=1NAj)+Nj=1NAj2)(2)=Ni=1NAi2(j=1NAj)2

ここまで変形してしまえば, ここまでスマートにコーディングすることができます.

# 入力
N = int(input())
A = [int(x) for x in input().split()]
# 出力
print(N * sum([x**2 for x in A]) - sum(A)**2)

D - Journey

最終的にグラフが連結になるためには, 最初にいる頂点1以外のN1個の頂点を新たに訪れなければなりません. つまり, 「今までに訪れていない頂点に新たに訪れる」ということをN1回行わなければなりません.

  • S:今までに訪れたことのある頂点の集合
  • |S|Sの要素数

とすると, 新しい頂点に訪れる確率は(N|S|)/Nとなります.

ここで, 確率p(p0)で成功する試行を, 成功するまで行うときの試行回数(最後の成功した回も含む)の期待値は1/pであることが知られています*1.

この事実を用いれば, |S|=1のときから|S|=N1のときまでN/(N|S|)を足し上げて,

(3)NN1+NN2++N1

が, 求める答えになります. これはO(N)で求めることが可能です.

# 入力
N = int(input())
# 答えを格納する変数
ans = 0.0
# 今まで訪れたことのない頂点を訪れるまでにかかる回数の期待値を加えていく
for i in range(1,N):
ans += N / (N - i)
# 出力
print(ans)

E以降

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

参考にしたサイト等

脚注

*1 :これは以下のようにして証明される.

Bernoulli試行(Bernoulli trial)(取り得る結果が「成功」「失敗」の2つのみであり, 各試行において成功の確率が同じであるランダム試行)を繰り返して初めて成功させるまでの試行回数Xの分布は, 幾何分布(geometric distribution)として知られている. 以下, そのBernoulli試行において, 「成功」する確率をp(p>0)とする. X=kのとき, k1回「失敗」した後に1回「成功」することになるので,

(4)P(X=k)=p(1p)k1

となる.

図 「成功」の確率がp=1/2, p=1/3, p=1/4, p=1/5の幾何分布.

この幾何分布の期待値を計算すると,

E(X)=k=1kP(X=k)=k=1kp(1p)k1=pk=1k(1p)k1=pk=1ddp(1p)k=pddpk=1(1p)k=pddp1p1(1p)(5)=1p

となる.

Search

About Me

自分の写真
理系大学生でした. Bloggerを利用して発信を行っています.

Labels

Blog Archives