2025-02-02 ABC064(A~C)

はじめに

お出かけ前にサクッと更新。

AtCoder Beginner Contest 064

A - RGB Cards
x, y, z = map(int, input().split())
number = 100 * x + 10 * y + z
print("YES" if number % 4 == 0 else "NO")

入力を一旦numberに格納して、それを4で割ってみている。
今見返すと100*xとかいうの別にみる必要ないですね。
ある数が4の倍数かどうかは、下2桁が4で割り切れるかで確認できるので。
(100が4で割り切れるので、100の倍数を何度足しても元の数を4で割ったあまりは変わりません)

B - Traveling AtCoDeer Problem
n = int(input())
a = list(map(int, input().split()))
print(max(a) - min(a))

行ったり来たりするのは面倒なので、左端から右端に向かって動けばOK、というコンセプトで解く。
校正箇所ゼロでした。

C - Colorful Leaderboard
n = int(input())
a = list(map(int, input().split()))

colors = set()
free_rankers = 0

for rating in a:
    if rating < 3200:
        colors.add(rating // 400)
    else:
        free_rankers += 1

min_rank = max(1, len(colors))
max_rank = len(colors) + free_rankers

print(min_rank, max_rank)

この人また問題文エスパーしてる……。変数名があまりにも的確ですね。
R3200未満の人は400で割った商で色分けされるので「colors」集合に放り込んで、
R3200以上の人は「free_rankers」として何人いるかをカウント。
あとは「colors」の中に何種類いるかとfree_rankersの人数から色の最小も最大も求まります。
最小はcolorsに誰もいないケースに注意。その時はfree_rankersが全員同じ色を選ぶ1色になります。
colorsに人がいるならそのうちの誰かの色を選ぶのが最小なので、colorsの種類と同等が最小値です。
最大はfree_rankersがとんでもない色を独立に選び続ければいいので、colorsの種類とfree_rankerの数をたした分が答えになります。
一旦min_rankとmax_rank変数においてからprintする部分と、各変数名のみが校正対象でした。

2025-02-01 ABC063(A~C)

はじめに

どこにも行く予定がない2月の土日、今日だけかもしれない。

AtCoder Beginner Contest 063

A - Restricted
# 入力: 2 つの整数 x, y
x, y = map(int, input().split())

# 合計が 9 以下なら合計を出力、それ以外は "error" を出力
result = x + y
print(result if result <= 9 else 'error')

いつもどおり一旦resultに格納した後に条件分岐させていくスタイル。

B - Varied
# 入力: 文字列 s
s = input()

# 重複がないかを判定
is_unique = len(s) == len(set(s))

# 結果を出力
print("yes" if is_unique else "no")

set(s)でsに含まれる文字の集合がつくれるので、そのサイズが元の文字列と同じなら
重複で消えてる文字がないということになる、という発想。
is_uniqueというBooleanに一度格納するのが「っぽい」作りですね。
1ヵ月見てきたらどういう実装を好みがちかなんとなくわかってきた。

C - Bugged
n = int(input())
scores = [int(input()) for _ in range(n)]

total_sum = sum(scores)

# 合計が10の倍数でない場合、そのまま出力
if total_sum % 10 != 0:
    print(total_sum)
else:
    # 10の倍数でない最小の数を探す
    scores.sort()
    for score in scores:
        if score % 10 != 0:
            print(total_sum - score)
            break
    else:
        # すべて10の倍数の場合は0を出力
        print(0)

とりあえず全部足してみて10の倍数じゃなければそれを出力すればOK。
10の倍数だったら、一番配点が低い問題を解かないのが一番ダメージが少ないのでそのパターンを試してみて、
それでもだめなら二番目に配点が低い問題を解かないでみて、……と順にやっていけばよい。
なお、「2問以上解かない」という択はあり得ない。そのうち片方解いた方が必ずマシだからだ。
(「問題1も問題2も10の倍数で、両者の合計が10の倍数ではない」というケースがありえないため)
ちなみに全問が10の倍数の配点だった場合は何をどう解いてもゼロしか表示されないので、そのことも場合分けしておく。
元実装は「else: pass」とか書いたりsum(s)を毎度毎度計算させたりで不要情報を大量に書いていたので、
修正後は結構すっきりした。for文の最後に「else」を書くの、まだ見慣れないなあ。

2025-01-29 ABC061(A~C)

はじめに

業務が落ち着いたので、ほっと一息。

AtCoder Beginner Contest 061

A - Between Two Integers
# 入力を取得
a, b, c = map(int, input().split())

# 範囲内判定
print('Yes' if a <= c <= b else 'No')

条件通りに判定するだけ。コメント入っただけ。

B - Counting Roads
# 入力を取得
n, m = map(int, input().split())

# 各頂点の次数を記録するリスト
degree = [0] * n

# 辺の情報を入力し、次数を更新
for _ in range(m):
    a, b = map(int, input().split())
    degree[a - 1] += 1
    degree[b - 1] += 1

# 結果を出力
print("\n".join(map(str, degree)))

道路の始点・終点を1本ずつ記録していって、最後に出力すればOK。GPTくんはグラフ理論の問題と捉えて、「degree」というリスト名にしていますね。
わたしは最後全部出力する系はいつもfor文で書いてますが、言われてみれば"\n".joinですっきり書けますね。
まあjoin関数重い説はあるっちゃありますが。

C - Big Array
# 入力取得
n, k = map(int, input().split())
villages = [tuple(map(int, input().split())) for _ in range(n)]

# 村を番号順にソート
villages.sort()

# 累積和で到達可能な村を探す
current_population = 0
for village, population in villages:
    current_population += population
    if current_population >= k:
        print(village)
        break

まさか配列をそのまま持つわけにはいかないので、ほんの少しだけ工夫が必要。
ということで「その数字を何個持ってるかリスト」を作っておけば、
その数字が小さい順にソートして個数を足していって、指定の数字を超えたところが答えになります。
GPTくんは「村番号とその人口」という捉え方をして、村番号が小さい順に人口を足していってK人を超えたところの
村番号を出力する、という解釈をしています。
実装内容は同じ……かと思いきや、またしてもわたしはwhile文、GPTくんはfor文で音楽性の違いが明らかに。
あと入力をタプルで持ってるのが一工夫ですかね。for文の書き方がスマートです。

2025-01-28 ABC060(A~C)

はじめに

1か月たったけどまだまだストックがある。実装も積もれば山となる。

AtCoder Beginner Contest 060

A - Shiritori
# 入力の3つの文字列を取得
a, b, c = input().split()

# aの最後の文字がbの最初の文字と一致し、かつ
# bの最後の文字がcの最初の文字と一致するか判定
result = 'YES' if a[-1] == b[0] and b[-1] == c[0] else 'NO'

print(result)

a[len(a)-1]などとしていたが、余裕で咎められている。
a[-1]で最後の文字が取れるの、なんとなく直感に合わないんですよね……。

B - Choose Integers
import math

# a, b, c を入力として取得
a, b, c = map(int, input().split())

# a と b の最大公約数が c の約数であるかを判定
result = 'YES' if c % math.gcd(a, b) == 0 else 'NO'

print(result)

問題文を整理するとAx≡C(mod B)となるx(≧0)があるかどうかの判定問題で、これをさらに変形すると
Ax=By+C⇔Ax-By=Cとなるようなx,y(≧0)が存在するかという問題になる。
ここでPx+Qy=1を満たすx,yが存在するためのP,Qの条件は「PとQが互いに素」なので、これのアナロジーでPとQの最大公約数がCなら上式を満たす。
ということで、実装よりも条件を考える方がつらそうな問題。実装はmath.gcdを使えば一発です。

C - Sentou
# 入力を取得
n, k = map(int, input().split())
times = list(map(int, input().split()))

# 合計時間を計算
total_time = 0
for i in range(n - 1):
    # 次の時刻までの間隔または k を追加
    total_time += min(times[i + 1] - times[i], k)

# 最後の区間分を追加
total_time += k

# 結果を出力
print(total_time)

愚直に愚直に。
①i(≠n)番目にボタンを押された時のお湯が出る時間は、次にボタンが押されるまでの時間とk秒のうちの短い方。
②n番目に押されたときは後ろに誰もいないので必ずk秒。
を分けて実装すればOK。実装も軽めなので、プログラムも変数名が変わったくらいです。

2025-01-26 ABC059(A~C)

はじめに

もう1月が終わる……?なぜ……?

AtCoder Beginner Contest 059

A - Three-letter acronym
# 入力を受け取り、各単語の先頭文字を取得して結合し、大文字に変換して出力
a, b, c = input().split()
print((a[0] + b[0] + c[0]).upper())

1文字目を抜き出して結合して大文字にする、というコメント1行目どおりのことを実行。
もっとスムーズな方法があるかと思ったけど、普通にゴリ押し。

B - Comparison
a = int(input())
b = int(input())

# a と b を比較して結果を出力
if a < b:
    print("LESS")
elif a > b:
    print("GREATER")
else:
    print("EQUAL")

pythonだと何が難しいかよくわからん系の問題。普通に条件分けするだけ。

C - Sequence
n = int(input())
a = list(map(int, input().split()))

# 累積和の計算
s = [0] * n
s[0] = a[0]
for i in range(1, n):
    s[i] = s[i - 1] + a[i]

# コスト計算
def calculate_cost(s, n, start_positive):
    cost = 0
    adjustment = 0
    for i in range(n):
        current = s[i] + adjustment
        if (i % 2 == 0) == start_positive:
            # 偶数インデックスが正のパターン
            if current <= 0:
                diff = 1 - current
                adjustment += diff
                cost += diff
        else:
            # 奇数インデックスが負のパターン
            if current >= 0:
                diff = current + 1
                adjustment -= diff
                cost += diff
    return cost

# 初項が正または負の場合の最小コストを計算
cost_positive_start = calculate_cost(s, n, True)
cost_negative_start = calculate_cost(s, n, False)

# 最小コストを出力
print(min(cost_positive_start, cost_negative_start))

結構考えた記憶がある問題。
まず累積和を作って、その累積和が①正→負→…または②負→正→…のどちらかになるように操作していって、①②の操作のコストが小さい方が答えとなる。
ここでは「cost」として操作の回数、「adjustment」としていくらオフセットされてるかを変数として持っている。
正となるべき時に負だったら1になるように、負となるべき時に正だったら-1になるように累積和をadjustmentしていく。
コストの計算としては、正となるべき時に負だったらその負値×(-1)+1、負となるべき時に正だったらその正値+1がかかる。
……みたいなことを実現するための関数が「calculate_cost」で、初項が+か-かを「start_positive」というBoolean変数で持つのが賢い。
元は初項がプラスになる時とマイナスになる時で冗長に2パターン書いてたけど、これなら統一的に判定できますね。

2025-01-25 ABC058(A~C)

はじめに

起きたら昼でしたが、土曜日はいつもこうなので猛気にしなくなってきました。

AtCoder Beginner Contest 058

A - ι⊥l
a, b, c = map(int, input().split())
print("YES" if 2 * b == a + c else "NO")

b-a==c-bで判定させてたのが移項されたりしている。

B - ∵∴∵
s1 = input()
s2 = input()

ans = "".join(s1[i] + s2[i] for i in range(min(len(s1), len(s2))))
ans += s1[len(s2):] + s2[len(s1):]

print(ans)

元々はかなりまどろっこしいことをしていて、
①Oの長さの1文字前まで交互に結合
②Oの最後の文字を追加
③OとEが同一の長さならEの最後の文字を追加
という流れで書いていました。
修正後はかなりシンプルで、①③をまとめてfor文からjoinにして、あとは余った分を結合する流れに。
なお問題の設定からしてEの方が短いので、最後の「+s2[len(s1)]:」は実は不要ですね。

C - 怪文書
n = int(input())
common_counts = [0] * 26  # 各アルファベットの共通カウント

# 最初の文字列の出現回数を基準として記録
s = input()
for char in s:
    common_counts[ord(char) - ord('a')] += 1

# 残りの文字列について共通する最小出現回数を更新
for _ in range(n - 1):
    s = input()
    current_counts = [0] * 26
    for char in s:
        current_counts[ord(char) - ord('a')] += 1
    for i in range(26):
        common_counts[i] = min(common_counts[i], current_counts[i])

# 共通部分を文字列として生成
result = "".join(chr(i + ord('a')) * common_counts[i] for i in range(26))
print(result)

やることは全入力の共通文字を拾い集めてちっちゃい順に並べる、と比較的単純なのに、実装がちょい重い感じの問題。GPTくんに整えてもらったけど苦心の跡がぬぐい切れてないですね?
コメントがやってること整理してくれているので追っかけていくと、
①各文字が何回使えるかカウントを用意、
②最初の文字列の文字数カウントを作成、
③次の文字列からは共通部分を探索すべく各文字のちっちゃい方を残していく、
④最後に結合という流れです。ord(char)-ord('a')で無理やり格納先を選定しているのが泣けるけど、元はもっと悲惨です。

2025-01-23 ABC057(A~C)

はじめに

睡眠に失敗し続けたのに日付が変わる直前に文章を書き始める異常行動具合。

AtCoder Beginner Contest 057

A - Remaining Time
# 時刻を24時間制で加算し、結果を出力
a, b = map(int, input().split())
print((a + b) % 24)

深夜26時はAM2時ですよね、ということで、足して24で割ったあまりが出したい答えです。コメントが増えただけ。

B - Checkpoints
def manhattan_distance(p1, p2):
    """Calculate Manhattan distance between two points."""
    return abs(p1[0] - p2[0]) + abs(p1[1] - p2[1])

n, m = map(int, input().split())

# Read the points
ab = [tuple(map(int, input().split())) for _ in range(n)]
cd = [tuple(map(int, input().split())) for _ in range(m)]

# Find the closest checkpoint for each point
for point in ab:
    closest_checkpoint = min(range(m), key=lambda j: manhattan_distance(point, cd[j]))
    print(closest_checkpoint + 1)

コメントが英語だ!!!どうした急に。ぐろーばりずむ化の波がここにも。
マンハッタン距離計算の関数はもともと用意してたので、そこは特に目新しさはなし。
むしろ答えを出しに行くところが面白くて、ラムダ式を使って答えを出しに行っている。この記事シリーズ書いててラムダ式出てきたの初めてですね。
これで「一番近いチェックポイントが複数ある場合には、番号が最も小さいチェックポイントに移動」を満たすのか一瞬不安になったけど大丈夫っぽい。

C - Digits in Multiplication
n = int(input())
min_digits = float('inf')

# n の約数を求め、その桁数の最大値の最小値を計算
for divisor in range(1, int(n**0.5) + 1):
    if n % divisor == 0:
        quotient = n // divisor
        max_digits = max(len(str(divisor)), len(str(quotient)))
        min_digits = min(min_digits, max_digits)

print(min_digits)

割り切れる全通りを試すだけの脳筋戦法。ただし探索対象はroundup(√n,0)までで良いことに注意(それ以上は相方で計算済みのため)。
ということで、nがdivisorで割り切れるならその相方をquotientと呼んであげて、divisorとquotientの桁数の大きい方をmax_digitsでおいて、
そのmax_digitsが最小になるようなmin_digitsを求めよう、という流れです。
これはただ変数名置き換えられて一旦quotientに入れる行が増えただけだ。まあやっていることが単純なのでね。