Atcoder - ABC339 勉強会まとめ
背景
Cykinso の 研究&データ技術開発 の山﨑です。
Cykinso 社内には競技プログラミング部があり、コンテストに参加している人していない人含め、ワイワイ問題を解いてます。
今回は ABC339 A, B, C, F をとりあげたので復習用にまとめておきます。
What's 競プロ?
競プロとは競技プログラミングの略です。
競技プログラミングとはコンピュータサイエンスに関する問題や純粋に実装が重い問題が出題されそれをバグを含まず速く解くことを競う競技です。
プログラミング学習をゲーミフィケーションした成功例とも言えます。
競プロのコンテスト
競技プログラミングの大会は「コンテスト」と呼ばれています。
海外も含めると週に3, 4回は開催されており毎週参加することができます。
日本で有名な競技プログラミングのサービスは以下の3つです。社内の部活動では最初のAtCoderと呼ばれている日本で一番規模が大きいサービスの問題を解いています。
海外まで範囲を広げるとさらに以下のようなサイトがあります。
A - TLD
問題文
英小文字と . のみからなる文字列
S が与えられます。
S を . で分割したときの末尾の文字列を出力してください。
すなわち、. を含まない
S の接尾辞のうち最長のものを出力してください。
.
が必ず1つ含まれるので Python なら split
で .
で分割し末尾の -1 を出力すればOKです。
ans = input().split(".")[-1]
print(ans)
ちなみにコンテスト中は気づかなかったですが TLD は Top Level Domain の略のようです。
B - Langton's Takahashi
トーラス状というのは簡単にいうとドラクエのフィールドのように世界の一番左端にいったら右端から出てくる状態です。したがってドラクエのマップみたいにちょこちょこ反対から出たり入ったりする移動を繰り返します。
現在、向いている方向を cur
という変数に整数で保存しておき、時計回りあるいは反時計回りに向きを変えるたびに +1
, -1
します。その結果 cur
が -1
や 4
になる場合があるので mod 4 をしておくのがポイントですね。
その後、実際に移動させてあげトーラス状なのでグリッドの端に行った場合の処理も mod
やや重目の実装ができますか?という感じの問題でした。
H, W, N = map(int, input().split())
G = [["."] * W for _ in range(H)]
directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
cur_direction = 0
cur_h = 0
cur_w = 0
for _ in range(N):
match G[cur_h][cur_w]:
case '.':
# 現在いるマスを黒にして時計回りに90度回転する
G[cur_h][cur_w] = '#'
cur_direction += 1
cur_direction %= 4
case '#':
# 現在いるマスを白にして反時計回りに90度回転する
G[cur_h][cur_w] = '.'
cur_direction -= 1
cur_direction %= 4
# 現在向いている方向に進む
dh, dw = directions[cur_direction]
nh = (cur_h + dh) % H
nw = (cur_w + dw) % W
cur_h = nh
cur_w = nw
for g in G:
print("".join(g))
C - Perfect Bus
途中のバス停で人が乗り降りしていて現在の乗客がマイナス
最初に
累積和を求めて一番低い値が
なお、一番低い値が負にならない時があるのでその場合の処理をしないといけないのに注意です。勉強会時、不覚にも WA になりました。
from itertools import accumulate
N = int(input())
A = list(map(int, input().split()))
acc = list(accumulate(A))
x = min(acc)
if x > 0:
ans = acc[-1]
else:
ans = -x + acc[-1]
print(ans)
なお、コンテスト中は 「最初に
N = int(input())
A = list(map(int, input().split()))
ng = -1
ok = 10 ** 19
def is_ok(x):
y = x
for a in A:
y += a
if y < 0:
return False
return True
while ok - ng > 1:
mid = (ok + ng) // 2
if is_ok(mid):
ok = mid
else:
ng = mid
ans = ok + sum(A)
print(ans)
F - Product Equality
勉強会では時間の関係もあり D, E を飛ばし、解説 AC でしたがやっていることがおもしろかった F に取り組みました。
まず制約が以下のように小さい時を考えます。
ぐらいなら B 問題ぐらいの難易度でしょうか?
これなら for を3回ループで全探索でカウントしても計算量は
A = list(map(int, input().split())
ans = 0
for i in range(N):
for j in range(N):
for k in range(N):
if A[i] * A[j] == A[k]:
ans += 1
print(ans)
次に
を考えます。今度は
そこで ans
が +1 され、そうでなければ +0 されるようになっています)
左辺のパートナーである右辺は多くてただ1つなので先程のように for を回す必要が実はないというイメージですね。計算量は
from collections import defaultdict
A = list(map(int, input().split())
D = defaultdict(int)
for a in A:
D[a] += 1
ans = 0
for i in range(N):
for j in range(N):
ans += D[A[i] * A[j]]
print(ans)
最後に F の問題の制約を考えてみます。
今度は
そこで
「
と仮定します。
結論から言うと、この仮定はうまくやれば成立するのですが、1つ問題点があります。
それは
例えば、
ではさらにどう工夫するか?
上記のように本来等しくない値同士が等しいとみなされてしまうことは、 mod をとることをハッシュ化とみなすと、 4 と 20 のハッシュが衝突していることが原因と言えます。
したがってハッシュがどのくらいの確率で衝突するか考えて、計算が大変じゃないかつ確率的にほぼ 0 の値になるように
では
例えば
すべてのハッシュの選び方は
一方、ハッシュが衝突しない選び方は
1個目のハッシュを選ぶ方法は
2個目のハッシュを選ぶ方法は
3個目のハッシュを選ぶ方法は
4個目のハッシュを選ぶ方法は
5個目のハッシュを選ぶ方法は
...
10個目のハッシュを選ぶ方法は
となります。
よって式にすると
と、 約62.8% と計算されます。逆に言えば「いずれかのハッシュが衝突する確率」は約37.2%と非常に高いです。
細かい計算は割愛しますが、
確率をちゃんと計算しといてイメージの話をしていしまいますが、箱を
以上のことから、
from collections import defaultdict
N = int(input())
A = [int(input()) for _ in range(N)]
x = 98080989074039189058908908209992
B = [a % x for a in A]
D = defaultdict(int)
for b in B:
D[b] += 1
ans = 0
for i in B:
for j in B:
ans += D[(i * j) % x]
print(ans)
ちなみに
余談ですが、逆に「
簡単に言うと「
Discussion