あるぱかのブログ

AtCoder茶(レート566)

AtCoder Beginner Contest 351

体調不調により不参加

A - The bottom of the ninth

リンク:A - The bottom of the ninth (atcoder.jp)

概要:野球をしていて9回表終了までの点数が配列 A Bで与えられます。9回裏(チーム Bの攻撃)で逆転サヨナラするために必要な点数を教えてね

チーム Bが勝つためにはこの回でチーム Aの総得点( =sum(A))を越える必要があります。

チーム Bの得点は sum(B)なので、両チームの総得点の差分+1点をこの回で得点できれば勝利することができます。

以下のコードでは入力をリストとして受け取ると同時に合計を計算させています。

#ABC351 A - The bottom of the ninth
A = sum(list(map(int, input().split())))
B = sum(list(map(int, input().split())))
print(A-B+1)  

提出リンク:提出 #52939398 - AtCoder Beginner Contest 351

B - Spot the Difference

リンク:B - Spot the Difference (atcoder.jp)

概要:文字列からなるグリッドが2つ与えられます。両者を比較すると1か所だけ異なる場所があるので座標を教えてね

問題文そのままです。

二次元盤面を受け取って全文字を探索し、異なる点を出力します。

入力の受け取りが0-indexed、要求される答えは1-indexedなので最後に1を足すことを忘れずに。

#ABC351 B - Spot the Difference
N = int(input())
A = []
for _ in range(N):
    a = input()
    A.append(a)
for i in range(N):
    a = A[i]
    b = input()
    for j in range(N):
        if a[j] != b[j]:
            print(i+1, j+1)  

提出リンク:提出 #52939558 - AtCoder Beginner Contest 351

C - Merge the balls

リンク:C - Merge the balls (atcoder.jp)

概要:様々な大きさのボールがやってきます。次の操作を行って最終的なボールの数を教えてね

  • ボールが1個なら操作終了
  • ボールが複数あり同じサイズのボールが2つ連続する場合はサイズ+1のボールに置き換える
  • ボールが複数あり隣り合うボールすべて違うサイズなら操作終了

 N \leq 2 \times 10^{5}なので愚直に処理するとTLEになるりそうな気がするけど順番が関係するのでそのまま処理するしかなさそう。

ひとまず問題文そのまま書いてみたところACできました。

計算量をどう考えたらいいのかよくわかりませんが、ボールを結合する操作は最大 N-1回しかしないので O(N)になるのでしょうか。

問題文ではボールのサイズは 2^{A_i}となっていますが A_iで管理しても変わりありません。

末尾にあるボールの取り出しはpop(-1)で行います。

(2回続けてpopするので両方とも末尾から取り出す形になります。)

配列に対する長さの取得(len)、末尾の取り出し(pop)、末尾に追加(append)いずれも計算量は O(1)です。

取り出すときと戻すときの順番に気を付ければそこまで難しくはないと思います。

#ABC351 C - Merge the balls
N = int(input())
A = list(map(int, input().split()))
ans = []
for i in range(N):
    ans.append(A[i])
#ボールが2個以上なら操作を試みる
    while len(ans) > 1:
#並び順で後になるボールをato、前になるボールをmaeとして取り出し
        ato = ans.pop(-1)
        mae = ans.pop(-1)
        if mae != ato:
#サイズが違うなら元の順番で戻す
            ans.append(mae)
            ans.append(ato)
            break
        else:
#同じサイズならサイズ+1を1つだけ追加する
            ans.append(mae+1)
print(len(ans))  

提出リンク:提出 #52939741 - AtCoder Beginner Contest 351

D - Grid and Magnet

リンク:D - Grid and Magnet (atcoder.jp)

概要:条件付き二次元盤面で各マスから最大何マス移動できるか探索して、その最大数を教えてね

いつもの壁と通路の二次元盤面ですが、今回は壁が磁石になっていて壁に隣接するマスに侵入はできますが脱出はできないという問題設定です。

また移動できる最大距離ではなく、全方位で合計何マス動けるか(=自由度)を問われています。

二次元盤面、探索ということで幅優先探索を使います。

基本方針は以下のようにします。

  • 現在地が通路であれば探索開始
  • 隣接する上下左右4マスを見て壁でなければ移動可能ということで自由度+1
  • 移動した先が壁に隣接するマスであればそれ以上移動はしない
  • 互いに移動可能なマスであれば自由度は同じなので、一度踏んだマスを記録しておいて2回以上同じ探索をしないようにする

ABC348Dで幅優先探索を書いていたので流用してジャッジ・・・TLE

#ABC351 D - Grid and Magnet
from collections import deque
def bfs(grid, sx, sy):
    DX = [1,-1,0,0]
    DY = [0,0,1,-1]
    H = len(grid)
    W = len(grid[0])
    q = deque()
    q.append((sx, sy))
    seen = [[False for j in range(W)] for i in range(H)]
    seen[sx][sy] = True
    while q:
        x, y = q.popleft()
        if grid[x][y] == 'N':
            continue
        else:
            for dx, dy in zip(DX,DY):
                xt, yt = x+dx, y+dy
                if xt < 0 or H-1 < xt or yt < 0 or W-1 < yt:
                    continue
                if grid[xt][yt] == '#':
                    continue
                if seen[xt][yt] == True :
                    continue
                else:
                    DOF[i][j] += 1
                    visited[xt][yt] = True
                    seen[xt][yt] = True
                    q.append((xt, yt))
H, W = map(int, input().split())
grid = []
for _ in range(H):
    grid.append(input())
#壁に隣接する部分を'N'として識別しやすくする
mas = [[0 for j in range(W)] for i in range(H)]
DX = [1,-1,0,0]
DY = [0,0,1,-1]
for x in range(H):
    for y in range(W):
        if grid[x][y] == '#':
            mas[x][y] = 1
            for dx, dy in zip(DX,DY):
                xt, yt = x+dx, y+dy
                if xt < 0 or H-1 < xt or yt < 0 or W-1 < yt:
                    continue
                if grid[xt][yt] == '#':
                    continue
                else:
                    mas[xt][yt] = 2
grid2 = []
for x in range(H):
    s = ''
    for y in range(W):
        if mas[x][y] == 0:
            s += '.'
        if mas[x][y] == 1:
            s += '#'
        if mas[x][y] == 2:
            s += 'N'
    grid2.append(s)
DOF = [[1 for j in range(W)] for i in range(H)]
visited = [[False for j in range(W)] for i in range(H)]
for i in range(H):
    for j in range(W):
        if visited[i][j] == False:
            if grid2[i][j] == '#':
                continue
            if grid2[i][j] == 'N':
                DOF[i][j] = 1
            else:
                bfs(grid2, i, j)
ans = 0
for i in range(H):
    for j in range(W):
        ans = max(ans, DOF[i][j])
print(ans)  

提出リンク:提出 #52945183 - AtCoder Beginner Contest 351

いろんなパターンで確認しても重複して探索はしていないのでそれ以外の部分の定数倍でTLEしてるっぽい。一晩寝て考える。

ひとまずグリッドを受け取った後に壁隣接部分を特定しやすいように処理している部分を消してみる・・・TLE。

もしかして探索済をマークする配列 seenの作成に毎回 O(HW)かかるのが重たいのでは?と思い集合型(set)に変えてみるとAC。

#ABC351 D - Grid and Magnet
def magnet(grid, x, y):
    global H
    global W
    div = [(1,0), (-1,0), (0,1), (0,-1)]
    magnet = False
    for dx, dy in div:
        xt, yt = x+dx, y+dy
        if xt < 0 or H-1 < xt or yt < 0 or W-1 < yt:
            continue
        if grid[xt][yt] == '#':
            magnet = True
    if magnet:
        return True  
from collections import deque
H, W = map(int, input().split())
grid = []
for _ in range(H):
    grid.append(input())
DOF = [[0]*W for _ in range(H)]
visited = [[False]*W for _ in range(H)]
ans = 1
for i in range(H):
    for j in range(W):
        if visited[i][j] == False:
            if grid[i][j] == '#':
                continue
            if magnet(grid, i, j):
                DOF[i][j] = 1
                continue
            else:
                DOF[i][j] += 1
                div = [(1,0), (-1,0), (0,1), (0,-1)]
                q = deque()
                q.append((i, j))
#探索済をマークする部分を配列からsetに変更
                seen = set()
                seen.add((i, j))
                visited[i][j] = True
                while q:
                    x, y = q.popleft()
                    if magnet(grid, x, y):
                        continue
                    for dx, dy in div:
                        xt, yt = x+dx, y+dy
                        if xt < 0 or H-1 < xt or yt < 0 or W-1 < yt:
                            continue
                        if grid[xt][yt] == '#':
                            continue
                        if (xt, yt) in seen:
                            continue
                        DOF[i][j] += 1
                        visited[xt][yt] = True
                        seen.add((xt, yt))
                        q.append((xt, yt))
                ans = max(DOF[i][j], ans)
print(ans)
#print(DOF)
#print(visited)

提出リンク:提出 #52999641 - AtCoder Beginner Contest 351

感想

本番出てたら30分でA~Cの3完というところでしょうか。

幅優先探索バグらせがちなので上手になりたい。

以下の学びがあったので次からは気を付けよう。

  • 何度もBFSする場合は探索済の管理を配列ではなくsetで行う
  • 上下左右4マスを見るときにin zip(DX、DY)としていたが(TLEしたコード)、zipを使わないほうが微妙に速い

ところでこのWA、TLEの数は何ですか????