かつてAtCoderを挫折した私から見た現在のADTとAIをベースとした自己完結的な精進スタイルの理想論

※本記事の内容は、すでにAtCoderを茶色で挫折した人間による、ただの想像や理想論に過ぎません。的外れなことを言っている可能性があることをあらかじめご了承ください。

【要旨:この記事で伝えたいこと】

かつてレートの乱高下に一喜一憂し、解説が理解できず「自分は無能だ」と自己肯定感を削られて挫折した私から、今学習している方へ伝えたい教訓は以下の3点です。

  1. 「週1回のABCの結果」を勝ち負けの指標にしない(ノイズが大きすぎるため)
  2. 「C問題まで解く」という難易度に依存した目標を立てない
  3. AIとAtCoder Daily Training(ADT)を活用し、フィードバックの頻度を上げる

現在は、私が苦しんだ「フィードバックの遅さと孤独な精進」を克服できる環境が整っていると思います。


1. 挫折の記録

私は2022年10月を最後にAtCoderを挫折しました。最高レート787から、緑(レート800以上)を目前にして連続でレートを落とし、最終的には691まで下がったところでのリタイアです。

当時の私にとって、唯一の「勝ち負け」は週に一度のABCによるレート変動でした。しかし、この構造には以下の欠陥がありました。

  • フィードバックの遅さ レートが下がると次回まで「負け」の状態が確定し、1週間ずっとストレスを抱えることになり、休日の2時間弱を無駄にしたと感じてしまう。
  • 指標の不安定さ 「C問題まで解く」と決めても、回によってCの難易度はブレる。解けない回が続くと、実力に関係なく無力感に襲われる。
  • 相対評価の罠 「緑になる」という目標は、自分の絶対的な成長ではなく「周囲との比較(相対評価)」に依存する。他者のレベルが上がれば、自分の実力が伸びていてもレートが停滞・下落する構造があり、達成感をコントロールできない。数ヶ月単位で「停滞という名の負け」を突きつけられ続ける可能性があり、精神的なコストが報酬を上回ってしまう。

2. AI以前の「精進」に伴う耐え難い無能感

当時は生成AIが普及しておらず、コンテスト後の解説記事を理解すること自体が大きな壁でした。 日本語で書いてあるはずの解説を読んでも理解できない時、突きつけられるのは「私は無能である」という自己否定感です。毎日、コンテストや精進のたびに自分の限界を突きつけられるような感覚は、学習を継続する意欲を確実に削いでいきました。


3. 今なら挫折しにくいと思う2つの理由

① AtCoder Daily Training (ADT) という救済

私が挫折した後に始まったAtCoder Daily Trainingは、まさに私が直面していた問題(フィードバック頻度の低さ)に対する素晴らしい解決策です。 週に何度も開催されるADTをベースに、「今日は何問解けたか」というアウトプットを指標にすれば、週1回のレート変動に一喜一憂する必要がなくなります。この仕組みを導入したAtCoderの運営判断は、挫折者を減らすための英断だと感じます。

② パーソナルメンターとしてのAI

今は解説が分からなければ、AIから即時フィードバックをもらえます。 「解説が理解できない=自分は無能」という思考のループに陥る前に、AIがストレスを劇的に軽減してくれます。


4. 結論:指標を「自分の行動」に置き換える

私が今AtCoderをやるなら、「コントロールできない、一週間に一度しかないレーティングの変動」ではなく、「コントロールできる精進の数や頻度の多いADTの結果」を指標とすることを重視するのが理想だと思います。

とはいえ、今の私にこれらを実践して復帰する気力はありません。私はAtCoderの問題をAIに解かせたこともなく、ADTに参加したこともないので、私の言っていることはただの想像でしかなく、そもそも的外れかもしれません。

また、今のAtCoderは、コンテスト中の生成AIの不正利用者が不公平なノイズを生むという当時とは異なるストレスがあるのかもしれません。そうした中で、モチベーションを維持するのは並大抵のことではありません。

現在、挫折せずAtCoderを続けている方々は、それだけで本当にすごいことだと思います。

AtCoder Beginner Contest 178 E - Dist Max【Python】

https://atcoder.jp/contests/abc178/tasks/abc178_e

AtCoder ProblemsでDifficulty: 1054、Solve Probability: 18%でした。

解けませんでした。
下記の記事の解説がわかりやすかったです。

bqn2.hatenablog.com

愚直に2点の距離を求めようとすると2重ループになるため1重ループで済むように式変形をする。
絶対値をmaxで分解するとmax((xi + yi) + (-xj - yj), ...)のようにiとjごとに分けられる。
各要素(xi + yi), (-xj - yj)....が最大なら全体も最大となる。
各要素(xi + yi), (-xj - yj)....を(x + y), (-x - y)....として一重ループで要素ごとの最大値を求めて変形した式に代入。

N = int(input())
XY = []
for i in range(N):
    XY.append(list(map(int, input().split())))

# x + y
a = 0
# -x - y
b = 0
# -x + y
c = 0
# x - y
d = 0
 
for i in range(N):
    x, y = XY[i]

    a = max(a, x + y)
    b = max(b, -x - y)
    c = max(c, -x + y)
    d = max(d, x - y)
    
print(max(a + b, d + c, c + d, b + a))

AtCoder Beginner Contest 153 E - Crested Ibis vs Monster【Python】

https://atcoder.jp/contests/abc153/tasks/abc153_e

AtCoder ProblemsでDifficulty: 1015、Solve Probability: 19%でした。

ちょうどアルゴ式でDPについて勉強したところだったため問題文を見てDPで解けそうという検討がつきました。

algo-method.com

H, N = map(int, input().split())
A = []
B = []
for i in range(N):
    a, b = map(int, input().split())
    A.append(a)
    B.append(b)
maxA = max(A)
dp = [[INF] * (H + 1 + maxA) for _ in range(N + 1)]
dp[0][0] = 0
for i in range(N):
    for j in range(H + 1 + maxA):
        if j - A[i] >= 0:
            dp[i + 1][j] = min(dp[i][j - A[i]] + B[i], dp[i + 1][j])
            # 同じ魔法を何度も使う場合
            dp[i + 1][j] = min(dp[i + 1][j - A[i]] + B[i], dp[i + 1][j])
        dp[i + 1][j] = min(dp[i + 1][j], dp[i][j])

print(min(dp[N][H:]))

AtCoder Beginner Contest 225 D - Play Train【Python】

https://atcoder.jp/contests/abc225/tasks/abc225_d

AtCoder ProblemsでDifficulty: 778、Solve Probability: 33%でした。

解けませんでした。
双方向リストも思いつかず、制約の「3xの形式のクエリで出力する電車の番号の個数の合計は106以下」についても、3xのクエリごとの番号の個数の合計が106以下の意味と捉えてしまっていました。
今後こういった制約の文については注意したいところです。

UNICORNプログラミングコンテスト2021(AtCoder Beginner Contest 225) - YouTube

N, Q = map(int, input().split())
front = [-1] * N
back = [-1] * N

for i in range(Q):
    q = list(map(int, input().split()))
    if q[0] == 3:
        x = q[1]-1
        while front[x] != -1:
            x = front[x]
        ans = [x+1]
        while back[x] != -1:
            x = back[x]
            ans.append(x+1)
        print(len(ans), *ans)
    else:
        x, y = q[1]-1, q[2]-1
        if q[0] == 1:
            front[y] = x
            back[x] = y
        elif q[0] == 2:
            front[y] = -1
            back[x] = -1

AtCoder Beginner Contest 167 D - Teleporter【Python】

https://atcoder.jp/contests/abc167/tasks/abc167_d

AtCoder ProblemsでDifficulty: 754、Solve Probability: 35%でした。

最終的に経路はリピートされるため、辞書で町ごとに訪れた回数をカウントし、初めてカウントが2になった街があれば、その時点からその街のカウントが3になるまでの経路をリストで管理しループから抜けるようにしました。

from collections import defaultdict

N, K = map(int, input().split())
A = list(map(int, input().split()))

d = defaultdict(int)
c = 0
d[c] += 1
rep_flag = False
rep = []
n_before_rep = 1
for i in range(K):
    c = A[c] - 1
    d[c] += 1
    if d[c] == 3:
        print(rep[(K + 1 - n_before_rep) % len(rep) - 1] + 1)
        exit()
    if d[c] == 2:
        rep_flag = True
    if rep_flag:
        rep.append(c)
    else:
        n_before_rep += 1
print(c + 1)

AtCoder Beginner Contest 243 D - Moves on Binary Tree【Python】

https://atcoder.jp/contests/abc243/tasks/abc243_d

AtCoder ProblemsでDifficulty: 758でした。

解けませんでした。
解説

atcoder.jp

の解法2の2進数に変換した文字列で考えるというのは部分的に他の問題でもあった記憶があるため思いつきたかったところです。

解説の解法1

from collections import deque
N, X = map(int, input().split())
S = input()
d = deque()

for i in range(N):
    s = S[i]
    if len(d) == 0:
        d.append(s)
    else:
        dl = d[-1]
        if (dl == 'R' or dl == 'L') and s == 'U':
            d.pop()
        else:
            d.append(s)

for s in d:
    if s == 'U':
        X //= 2
    elif s == 'L':
        X *= 2
    else:
        X = X * 2 + 1
print(X)

解説の解法2

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

T = list(bin(X))
for i in range(N):
    s = S[i]
    if s == 'U':
        T.pop()
    elif s == 'L':
        T += '0'
    else:
        T += '1'
print(int(''.join(T), 2))

AtCoder Beginner Contest 241 C - Connect 6【Python】

https://atcoder.jp/contests/abc241/tasks/abc241_c

AtCoder ProblemsでDifficulty: 664でした。

コンテスト中に解けませんでした。6行6列のマス目を動かして全探索するという発想ができませんでした。

N = int(input())
S = []
for i in range(N):
    S.append(input())

for i in range(N-5):
    for j in range(N-5):
        for k in range(6):
            count = 0
            for l in range(6):
                if S[i+k][j+l] == '#':
                    count += 1
            if count >= 4:
                print('Yes')
                exit()
            
        for k in range(6):
            count = 0
            for l in range(6):
                if S[i+l][j+k] == '#':
                    count += 1
            if count >= 4:
                print('Yes')
                exit()
        count = 0
        for k in range(6):
            if S[i+k][j+k] == '#':
                count += 1
        if count >= 4:
            print('Yes')
            exit()
        count = 0
        for k in range(6):
            if S[i+k][j+5-k] == '#':
                count += 1
        if count >= 4:
            print('Yes')
            exit()
print('No')