🔥

【AtCoder】Daily Training 2025/03/25

に公開

1. はじめに

目的

Daily Trainingを解くことにより、競技プログラミングに慣れることを目的としています。

フィードバックして欲しい点

私は競技プログラミング初心者のため、いただいたアドバイスをすぐに理解できず、何度も質問してしまうかもしれません。それでも構わないという心優しい方がいらっしゃれば、ぜひアドバイスをいただけると嬉しいです。丁寧に教えていただけると、とても助かります!

  1. アルゴリズムの選択と最適化
    • 今の実装よりも効率的なアルゴリズムがあるか?
    • 計算量を減らせる部分や、ボトルネックになっている処理はないか?
  2. コードの可読性と整理
    • もっとシンプルに書ける部分はないか?
    • 変数名や関数名が分かりやすいか?
  3. エッジケース・例外処理の検討
    • 想定外の入力でバグが発生する可能性がないか?
    • 競プロのジャッジでWAになりそうなケースがあるか?

注意点

  • 言語はPython(PyPy 3.10-v7.3.12) を使用しています。
  • 今回はバーチャル参加ではありません。

2. コンテスト情報

コンテスト名

AtCoder Daily Training EASY 2025/03/25 15:30start

コンテストURL

https://atcoder.jp/contests/adt_easy_20250325_1

開催日

2025-03-25(火) 15:30 ~ 2025-03-25(火) 16:30 (60分)

配点

問題 点数
A (過去のABCのA問題から出題) 100
B (過去のABCのA問題から出題) 100
C (過去のABCのA問題から出題) 200
D (過去のABCのA問題から出題) 200
E (過去のABCのA問題から出題) 300くらい

3. 解いた問題と解法

A: Not Too Hard

コード

N, X = map(int, input().split())
S = list(map(int, input().split()))
count = 0

for i, v in enumerate(S):
  if v <= X:
    count += v

print(count)

解法

enumerate関数を用いることで、イテラブルオブジェクトの要素を取得する。
そして、条件分岐を用いて、X以下の値vのみをcountに足し合わせる。

改善点

- for i, v in enumerate(S):
+ for i in S:

インデックスを使用しなかったため、リストの要素取得で十分である。

B: 321-like Checker

コード

N = str(input())

if len(N) == 1:
  print("Yes")
  
else:
  for i in range(len(N)-1):
    if int(N[i]) <= int(N[i+1]):
      print("No")
      break
  else:
    print("Yes")

解法

1桁の正の整数は必ず321-like Numberになるので、条件分岐でN1桁の場合にYesとする。
また、常にi桁目の値がi+1桁目に比べて、減少していく必要があるので、i桁目の値とi+1桁目の値が1回でも等しくなる or 増加すれば、Noでループを抜ける。
そして、for文が最後まで実行される、すなわち単調減少となればYesにする。

改善点

数値のまま受け取り、10で割ったあまりをリストに格納し、、、ということもできる。

- if len(N) == 1:
-   print("Yes")
-  
- else:

len(N)が1の時、len(N)-10なので、ループ処理が行われずYesが出力される。そのため、条件分岐は必要ない。

C: Strawberries

コード

N, K = map(int, input().split())
S = input()

print(S.count("O"*K))

解法

countメソッドを使うことで、"0"*Kという文字列が文字列Sの中にいくつ存在するかを数える。
また、"0"*Kとすることで、連続するK本の歯を表している。

改善点

+ if i[i:i+k] == list("0"*K):
+   ans += 1
+   for j in range(i, i+k):
+.    N[j] = "X"

全ての歯について"0"*Kを調べ、条件を満たせば食べられる回数を+1にし、使った歯をXにすることでも調べることができる。

D: Modulo Number

コード

N = int(input())

for i in range(998244353):
  if (N-i) % 998244353 == 0:
    print(i)

解法

N-i998244353の倍数であるかを、%演算子を使って余りが0になるかで判別する。

改善点

N - i = 998244353k \iff N = 998244353k + i
N = int(input())
print(N % 998244353)

N998244353で割った余りが回答となるので、ループを利用する必要はない。

E: Submask

コード

N = int(input())
bit = []

for i in range(len(format(N, "b"))):
  if N & (1<<i):
    bit.append(i)
    
for i in range(1<<len(bit)):
  x = 0
  
  for j in range(len(bit)):
    if i & (1<<j):
      x |= (1<<bit[j])
  print(x)

解法

N2^kの位の値が1となる時のみ、x2^kの位の値は01の両方を取ることができる。
そのため、リストbitにN2^kの位の値が1となるインデックスをリストに格納する。
そして、1となる位がk個あれば、2^kだけxが存在するので、1<<len(bit)10進数回だけループを行う。

N=112進数表記すると、1011なので、条件を満たすxは以下の表のようになる。

変換前 変換後
0000 000
0001 001
0010 010
0011 011
1000 100
1001 101
1010 110
1011 111

上記の表のように、この1の位の集合のみで、新たな二進数が形成されると考えると、変数iを用いることでxのすべてのパターンを表せる。
よって、変数iの位の値が1となる場合に、1<<bit[j]で本来の位のインデックスに直し、xと論理和をとっていくことで、本来のxに直せる。

改善点

def generate_submasks(N):
    submasks = [0]
    for i in range(60):
        if N & (1<<i):
            submasks += [x|(1<<i) for x in submasks]
    return submasks

N = int(input())
results = generate_submasks(N)
# results.sort()
for result in results:
    print(result)

上記のコードのように、集合を徐々に大きくして構成する方法もある。
まずは、N2^kの位が1の値となる場合にのみリストの中身を変化させる。
そして、kbitが1になる場合に、kbit未満の2進数をすべて足し合わせていくことにより、すべてのxを表す。

4. 他の解法・考察

別のアプローチの可能性

  • 問題Eでは集合を拡張するアプローチが新鮮だった。

計算量や工夫点

  • int型とstr型をうまく使い分けられた点は非常に良かった。
  • 計算量を意識して、冗長なループを避けた

5. まとめ

今回の気づき

  • ビット操作を活用した解法の理解が進んだ。
  • 異なるアプローチの比較が重要

改善したい点

  • 時間制限内に解くスキルを向上させる。
  • C++の解法を研究し、Pythonでの最適な書き方を模索する。

Discussion