AtCoder Beginner Contest 246をPythonで解く

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

Atcoder Python

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

こんにちは, Shinoryoです.

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

A - Four Points

解法パターンA

x座標のリスト, y座標のリストを用意し, それぞれのリストで登場回数が1回の座標の組み合わせが求める座標になります. リストにおける登場回数を調べるには, collectionsCounterを用いるとよいです.

import collections
# 入力
x_zahyou = []
y_zahyou = []
for _ in range(3):
x_zahyou_temp, y_zahyou_temp = [int(x) for x in input().split()]
x_zahyou.append(x_zahyou_temp)
y_zahyou.append(y_zahyou_temp)
# x座標の出てくる回数をカウントし、
# 最も少ないものの座標値を取得
x_count = collections.Counter(x_zahyou)
x_ans = x_count.most_common()[-1][0]
# y座標の出てくる回数をカウントし、
# 最も少ないものの座標値を取得
y_count = collections.Counter(y_zahyou)
y_ans = y_count.most_common()[-1][0]
# 出力
print(x_ans, y_ans)

解法パターンB(こちらの方が標準的っぽい)

上記の「登場回数が1回の座標の組み合わせ」というのを, 別の方法で求めます. 入力するx座標をx1,x2,x3, 求めるべきx座標をxansとします.

  • x1=x2ならば, xans=x3
  • x1=x3ならば, xans=x2
  • x2=x3ならば, xans=x1

以上のようにして, xansを求めることができます. 問題文にある「制約」から, この3つのどれか1つにかならず当てはまります.

y座標についても同様になります.

# 入力
x_zahyou = []
y_zahyou = []
for _ in range(3):
x_zahyou_temp, y_zahyou_temp = [int(x) for x in input().split()]
x_zahyou.append(x_zahyou_temp)
y_zahyou.append(y_zahyou_temp)
# x座標を求める
if x_zahyou[0] == x_zahyou[1]:
x_ans = x_zahyou[2]
elif x_zahyou[0] == x_zahyou[2]:
x_ans = x_zahyou[1]
else:
x_ans = x_zahyou[0]
# y座標を求める
if y_zahyou[0] == y_zahyou[1]:
y_ans = y_zahyou[2]
elif y_zahyou[0] == y_zahyou[2]:
y_ans = y_zahyou[1]
else:
y_ans = y_zahyou[0]
# 出力
print(x_ans, y_ans)

B - Get Closer

求めるのは,

(1)(AA2+B2,BA2+B2)

になります.

# 入力
A, B = [int(x) for x in input().split()]
# 出力
print(A / (((A**2) + (B**2))**(1/2)), B / (((A**2) + (B**2))**(1/2)))

C - Coupon

クーポンによる値下げには, 以下の2つのタイプがあります. クーポン使用前の値段をa円とします.

  • タイプ1:aXのときは, aからX円ぴったりが引かれる.
  • タイプ2:a<Xのときは, aからa円のみが引かれる.

全商品を買うための合計費用を最小化するためには, より効率的にクーポンを使用していく, つまりタイプ1の値下げをできる限り行った後に, タイプ2の値下げを行えばよいわけです.

タイプ1の値下げに関して考えます. i番目の商品に関してAiからXをできる限り引くことを考えるわけですが, 可能な回数は

(2)AiX

となります(ただし, xはGauss記号であり, xを超えない最大の整数). クーポンはK枚しかありませんので, タイプ1の値下げに使えるクーポンの枚数は

(3)min{i=1NAiX,K}

となります.

タイプ2の値下げに関して考えます. タイプ1のような値下げを全て仕切ってなお, クーポンが余っている場合を考えますので, Aiは全てXで割った余りに変換してしまいます. その上で, Aiを大きい順にソートして, クーポンがある限り順番にタイプ2の値下げを行っていきます.

以上により, 全商品を買うための最小合計費用を求めることができます.

# 入力
N, K, X = [int(x) for x in input().split()]
A = [int(x) for x in input().split()]
# 最後の出力のため
A_original = [a for a in A]
# 使うクーポン数を格納
use_coupon = 0
# 減額できる値段を格納
discount = 0
# A_1から順番に0円を下回らない範囲でクーポンを適用する
for i in range(N):
if A[i] >= X:
use_coupon += (A[i] // X)
# 使うクーポンはKより小さくなければならない
use_coupon = min(use_coupon, K)
# 残りクーポン数を計算
K -= use_coupon
# ここまでで減額できる額を計算
discount = use_coupon * X
# ここまでクーポンを使った後の端数の処理
# 大きいのから順に引いていきたいので、端数を計算した後でソートする
for i in range(N):
A[i] %= X
A.sort(reverse=True)
# クーポンがある限り、大きい端数から消していく
for i in range(N):
if A[i] > 0 and K > 0:
K -= 1
discount += A[i]
# 出力
print(sum(A_original) - discount)

D - 2-variable Function

ある整数Xが条件に当てはまるかを考えるのは非常に難しいので, さまざまなa,bに対してf(a,b)=a3+a2b+ab2+b3の値を格納したリストを用意して, そのリストの中からN以上の最小の整数を持ってきてXにすることが考えられます. さらに具体的に手法を詰めていくと, 固定されたaに対してf(a,b)bに関して単調増加であることを利用して, 以下のように求められます.

  1. 答えの初期値として, ansを無限大で用意する.
  2. aの値を定める(0から106までループ).
    1. b=0としてf(a,b)の値を求める.
    2. f(a,b)<Nの場合は, b+=1として上に戻る.
    3. f(a,b)Nの場合は, ans=min(ans,f(a,b))のように更新して, bに関するループを終了する.
  3. 結果として得られるansが答え.

なお, N=1018であり, a=106, b=0とするとf(a,b)=1018であるので, a, bの調べる範囲は0から106まででいいことが分かることを利用しています. しかし, それらの全範囲を探索しているだけではとても時間が足りません.

そこで, aに関するループはそのままに, 固定されたaに対してf(a,b)bに関して単調増加であることをさらに利用して, 二分探索(binary search)を用いることを考えます.

  • あるlow(適切なbがある範囲の下限)とあるhigh(適切なbがある範囲の上限)の値を用意する.
  • lowhighの真ん中の値として, mid=(low+high)/2を用意する.
  • f(a,mid)を求める.
  • f(a,mid)の値によって, 以下のように分類する.
    • f(a,mid)<Nの場合は, bとしてはmidより大きい値を取ることになるので, low=midとする.
    • f(a,mid)Nの場合は, bとしてはmid以下の値を取ることになるので, high=midとする.
  • lowhighの差が1になるまで, これを繰り返す.

このようにしていくことで, 最終的にhighが, 固定されたaに対して適切なbになります. 答えの初期値として, ansを無限大で用意して, 各aに対してループしていく過程でf(a,high)によってansを更新していけば, 最終的なansが求める答えとなります.

# 入力
N = int(input())
# a,bからa^3+a^2b+ab^2+b^3を求める関数
def func(a, b):
return (a**3) + (a**2) * b + a * (b**2) + (b**3)
# 答えを格納する変数
ans = float("inf")
# X = a^3+a^2b+ab^2+b^3を満たすXに関して、N以上で最小のものを探す
# 10^18がXの条件を、a = 10^6、b = 0で満たすので、aとして0~10^6を探索する
for a in range(0, (10**6) + 1):
# bに関しても0~10^6を探索する
# bに関してはf(a, b)が単調増加なので、二分探索を行う
low = -1 # 答えになるhighが0をとりうるように、-1にする
high = 10**6
# 二分探索を実施
# lowとhighの差が1になるまで続ける
while low + 1 < high:
# lowとhighの平均をとる(小数点以下切り捨て)
mid = (low + high) // 2
# f(a, mid)の値を取得
value = func(a, mid)
# value < Nであれば、lowはmid以上の値であることが分かる
if value < N:
low = mid
# value >= Nであれば、highはmid以下の値であることが分かる
else:
high = mid
# 得られたhighが、ansになりうるb
# (func(a, high) >= Nで、highは限りなく小さい)
# ansを更新する
ans = min(ans, func(a, high))
# 出力
print(ans)

二分探索については「SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192)をPythonで解く」でも取り上げていますので, ぜひご覧ください.

E以降

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

参考にしたサイト等