ABC348 with Python (A～E)

ABC348のA~E問題をpythonで解説していきます。今回のC問題は平和でしたね(・∀・)

# A - Penalty Kick
## 問題
<p>高橋君はサッカーの試合で $N$ 回ペナルティキックを蹴ります。</p>
<p>高橋君は $i$ 回目のペナルティキックでは、$i$ が $3$ の倍数の場合は失敗し、それ以外の場合は成功します。</p>
<p>高橋君がペナルティキックを蹴ったときの結果を出力してください。</p>

https://atcoder.jp/contests/abc348/tasks/abc348_a
## 考察
問題文の通り、3の倍数回目のペナルティキックでは<code>x</code>、それ以外は<code>o</code>とすればよい。

## コード
```python:A.py
N = int(input())
ans = []
for i in range(1, N + 1):
    if i % 3 == 0: # 3の倍数回目ならば失敗
        ans.append("x")
    else:
        ans.append("o")
print("".join(ans))
```
# B - Farthest Point
## 問題
<p>$xy$ 平面上に $1$ から $N$ までの番号が付いた $N$ 個の点があります。点 $i$ は座標 $(X_i, Y_i)$ にあり、相異なる $2$ 点の座標は異なります。</p>
<p>各点について、その点からの距離が最大である点を求めてその点の番号を出力してください。
ただし、距離が最大である点が複数ある場合はその中で最も番号が小さい点の番号を出力してください。</p>
<p>ここで、距離はユークリッド距離、すなわち $(x_1,y_1)$ と $(x_2,y_2)$ に対し、この $2$ 点間の距離が $\sqrt{(x_1-x_2)^{2}+(y_1-y_2)^{2}}$ であるものとして定められています。</p>

https://atcoder.jp/contests/abc348/tasks/abc348_b
## 考察
各点について、ユークリッド距離が最大である点を求める、もし複数あるならば、最も番号が小さい点を選ぶ、ということなので、各点において、番号が小さい順に点を見ていき、距離の最大値が更新されたらそれをansとすればよい。ここで、ユークリッド距離の大小の比較において、平方根をとっても取らなくても結果は変わらないため、今回は誤差を嫌って平方根をとらないことにした。

## コード
```python:B.py
N = int(input())
XY = [list(map(int, input().split())) for _ in range(N)]
INF = float("inf")
for i in range(N):
    max_dist = -INF
    xi, yi = XY[i]
    for j in range(N):
        xj, yj = XY[j]
        dist = (xi - xj) ** 2 + (yi - yj) ** 2 # 平方根をとらない
        if max_dist < dist:
            ans = j + 1
            max_dist = dist
    print(ans)
```
# C - Colorful Beans
## 問題
<p>$N$ 種類のビーンズが $1$ 粒ずつあります。 $i$ 種類目のビーンズはおいしさが $A_i$ で色が $C_i$ です。ビーンズは混ぜられており、色でしか区別することができません。</p>
<p>あなたはビーンズの色を $1$ つ選び、その色のビーンズをどれか $1$ 粒食べます。ビーンズの色をうまく選ぶことで、食べる可能性のあるビーンズのおいしさの最小値を最大化してください。</p>

https://atcoder.jp/contests/abc348/tasks/abc348_c

## 考察
辞書を使って、各色のビーンズの美味しさの最小値を記録、その後それらの最大値を出力する。今回はdefaultdict用い、初期値を無限大とした。

## コード
```python:C.py
from collections import defaultdict

N = int(input())
INF = float("inf")
beans = defaultdict(lambda: INF) # 初期値無限大
for _ in range(N):
    a, c = map(int, input().split())
    beans[c] = min(beans[c], a) # 色ごとに最小値を更新
print(max(beans.values()))
```
# D - Medicines on Grid
## 問題
<p>$H$ 行 $W$ 列のグリッドがあります。上から $i$ 行目、左から $j$ 列目のマス目を $(i, j)$ と表します。各マスの状態は文字 $A_{i,j}$ で表され、意味は以下の通りです。</p>
<ul>
<li><code>.</code> : 空きマス。</li>
<li><code>#</code> : 障害物。</li>
<li><code>S</code> : 空きマスかつスタート地点。</li>
<li><code>T</code> : 空きマスかつゴール地点。</li>
</ul>
<p>高橋君は、今いるマスから上下左右に隣り合う空きマスへ、エネルギーを $1$ 消費して移動することができます。ただし、エネルギーが $0$ の状態で移動することはできず、またグリッドの外へ移動することはできません。</p>
<p>グリッドには合計で $N$ 個の薬があります。$i$ 番目の薬は空きマス $(R_i, C_i)$ にあり、使うとエネルギーを<strong> $E_i$ にする</strong>ことができます。必ずしもエネルギーが増えるとは限らないことに注意してください。高橋君は自分のいるマスにある薬を使うことができます。使った薬はなくなります。</p>
<p>高橋君ははじめエネルギー $0$ の状態でスタート地点にいて、ゴール地点まで移動したいです。これが可能かどうか判定してください。</p>

https://atcoder.jp/contests/abc348/tasks/abc348_d
## 考察
今回は薬を取る度に全マス更新し直しても$O(NHW)$で間に合うため、単純なBFSでも解けるが、後学のために優先度付きキューを用いた方法で実装する。これは、最も残りエネルギーが高いマスから更新を進めていくものであり、適当にマスを選んで更新していくよりも、更新回数を減らすことが期待できる。(解説によると、実際に計算量が減っている証明はできていないそうだが、テストケースではこちらの方が高速で動作する)
あとは、ゴールマスにたどり着ければ<code>Yes</code>、そうでなければ<code>No</code>と出力すればよい。

## コード
```python:D.py
import heapq

dxdy = ((0, 1), (0, -1), (1, 0), (-1, 0))  # 上下右左
H, W = map(int, input().split())
MAP = [list(input()) for _ in range(H)]
energy = [[0] * W for _ in range(H)]
N = int(input())
for _ in range(N):
    r, c, e = map(int, input().split())
    r -= 1
    c -= 1
    energy[r][c] = e
for i in range(H):
    for j in range(W):
        if MAP[i][j] == "S":
            sy, sx = i, j
        elif MAP[i][j] == "T":
            gy, gx = i, j
dist = [[-1] * W for _ in range(H)]
pq = []
heapq.heappush(pq, (-energy[sy][sx], sy, sx))
while pq:
    d, y, x = heapq.heappop(pq)
    d *= -1
    if dist[y][x] > d:
        continue
    dist[y][x] = d
    if dist[y][x] == 0:
        continue
    for dx, dy in dxdy:
        nx = x + dx
        ny = y + dy
        if 0 <= nx < W and 0 <= ny < H and MAP[ny][nx] != "#":
            if ny == gy and nx == gx:
                print("Yes")
                exit()
            if dist[ny][nx] >= dist[y][x] - 1:
                continue
            dist[ny][nx] = max(dist[y][x] - 1, energy[ny][nx])
            heapq.heappush(pq, (-dist[ny][nx], ny, nx))
print("No")
```
# E - Minimize Sum of Distances
## 問題
<p>$N$ 頂点からなる木が与えられます。頂点は $1$ から $N$ までの番号がついており、 $i$ 番目の辺は頂点 $A_i, B_i$ を結んでいます。</p>
<p>長さ $N$ の正整数列 $C = (C_1, C_2, \ldots ,C_N)$ が与えられます。$d(a, b)$ を頂点 $a, b$ の間にある辺の数とし、 $x = 1, 2, \ldots ,N$ に対して $\displaystyle f(x) = \sum_{i=1}^{N} (C_i \times d(x, i))$ とします。$\displaystyle \min_{1 \leq v \leq N} f(v)$ を求めてください。</p>

https://atcoder.jp/contests/abc348/tasks/abc348_e
## 考察
説明のために入力例1を例に挙げる。
まず思考の過程として、$C_i$が一番大きな頂点との距離を最小にするために、$f(4)$を考えるが、出力例1にもある通り、これは最小ではない。ここから、貪欲に最小となる頂点$v$を決めるのは無理そうだと考える。
そうなると、後はそれぞれの頂点について、$f(v)$を高速に求めることで最小値を求める全探索的手法を考えるが、これは、$f(1)$をあらかじめ求めておくことで可能であることが考察するとわかる。具体的には、まず$f(1)$を求めると、下図のような状態になる。

![now.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3426433/4fb732b5-b820-186e-4e5e-a161ceb722f7.png)

ここから、隣の頂点2の$f(2)$を求める手順は以下となる。

![nxt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3426433/3b60523f-c109-9034-ae3a-e9417a30472d.png)

これを実装する関係上、下図のようなmemoをdfsで作成した。

![end.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3426433/935d6ca9-e14d-b26f-b513-b230c3363669.png)



## コード
```python:E.py
import sys
from types import GeneratorType
from collections import deque

sys.setrecursionlimit(int(1e7)) # 再起上限解放


def bootstrap(f, stack=[]):  # 再帰高速化用、yieldを使う
    def wrappedfunc(*args, **kwargs):
        if stack:
            return f(*args, **kwargs)
        else:
            to = f(*args, **kwargs)
            while True:
                if type(to) is GeneratorType:
                    stack.append(to)
                    to = next(to)
                else:
                    stack.pop()
                    if not stack:
                        break
                    to = stack[-1].send(to)
            return to

    return wrappedfunc


N = int(input())
adj = [[] for _ in range(N)]
for _ in range(N - 1):
    a, b = map(int, input().split())
    a -= 1
    b -= 1
    adj[a].append(b)
    adj[b].append(a)
C = list(map(int, input().split()))


# f(1)を求める
dist = [N + 10] * N
q = deque([(0, 0)])
while q:
    d, node = q.popleft()
    dist[node] = d
    for nxt in adj[node]:
        if dist[nxt] > dist[node] + 1:
            dist[nxt] = dist[node] + 1
            q.append((dist[nxt], nxt))
ans = [-1] * N
ans[0] = 0
for i in range(N):
    ans[0] += dist[i] * C[i]

# memoの作成
memo = [-1] * N


@bootstrap
def dfs(pre, node):
    if memo[node] != -1:
        yield memo[node]
    res = C[node]
    for nxt in adj[node]:
        if pre == nxt:
            continue
        tmp = yield dfs(node, nxt)
        res += tmp
    memo[node] = res
    yield res


dfs(-1, 0)
SUM = sum(C)


# f(node)とmemoを基に、f(nxt)をO(1)で求める
@bootstrap
def dfs2(node, now):
    for nxt in adj[node]:
        if ans[nxt] != -1:
            continue
        ans[nxt] = now - memo[nxt] + (SUM - memo[nxt])
        yield dfs2(nxt, ans[nxt])
    yield None


dfs2(0, ans[0])
print(min(ans))
```