ABC302をPythonで解いてみたよ。(A～E問題)

[AtCoder Beginners Contest 302 (ABC302)](https://atcoder.jp/contests/abc302) をPythonで解きました。
見やすいコードを書く練習も兼ねてます。


# A - Attack
## 問題ページ
difficulty: <font color="gray">24</font>

https://atcoder.jp/contests/abc302/tasks/abc302_a

## 入力
$A$: 敵の体力
$B$: 1回の攻撃で削れる体力

## 考察
小数点切り上げの割り算が $(A+B-1)//B$ なのを知ってたらすぐに解けます。

```Python
A, B = map(int, input().split())
ans = (A + B - 1) // B
print(ans)
```

$A//B$ で小数点以下切り捨ての割り算をしてみて、
もし$A÷B$の余りが0より大きいときに、答えに1を足すようにしてもACできます。
```Python
A, B = map(int, input().split())
ans = A // B
if A % B > 0:
    ans += 1
print(ans)
```

# B - Find snuke
## 問題ページ
difficulty: <font color="gray">352</font>

https://atcoder.jp/contests/abc302/tasks/abc302_b

## 入力
$H$: 縦の長さ
$W$: 横の長さ
$S_{i,j}$: $i$行目$j$列目に書いてある文字

## 考察
8方向のベクトルを事前に用意します。
各点について8方向を見て、"snuke"になってたら答えを出力します。
枠外を見に行くとエラーになるので気をつけましょう。

## コード
```Python
H, W = map(int, input().split())
S = [input() for _ in range(H)]

# 縦・横・斜めの8方向のベクトルをvecsに入れる。
vecs = []
for vi in [-1, 0, 1]:
    for vj in [-1, 0, 1]:
        if (vi, vj) != (0, 0):
            vecs.append((vi, vj))

# 点(i, j)について考える。
for i in range(H):
    for j in range(W):
        # 点(i, j)から8方向を見て、もしsnukeになってたら答えを出力する。
        for vi, vj in vecs:
            # H * W の枠外に出てしまうものを除外する。
            gi, gj = i + vi * 4, j + vj * 4
            if not (0 <= gi < H and 0 <= gj < W):
                continue

            now_str = ''
            ans_list = []
            for k in range(5):
                ni, nj = i + vi * k, j + vj * k
                now_str = now_str + S[ni][nj]
                ans_list.append((ni + 1, nj + 1))  # 答えは1-indexedで出力する。
            if now_str == 'snuke':
                for ans in ans_list:
                    print(*ans)
```

# C - Almost Equal
## 問題ページ
difficulty: <font color="brown">469</font>

https://atcoder.jp/contests/abc302/tasks/abc302_c

## 入力
$N$: 入力される文字列の個数
$M$: 文字列の長さ
$S_i$: $i$個目の文字列


## 考察
「順列全探索」と呼ばれるタイプの問題です。
$N$も$M$もめちゃくちゃに小さいから、並び替えたのを全通り試してみます。
itertoolの中にあるpermutationsを使うと簡単でオススメです！

2つの文字列を比較するところは、for文の中でコードを書いてもいいんですが、分けて関数にしておくとコードも思考もすっきりしがちです！

## コード

```Python
from itertools import permutations


# 文字列s1を何文字変更したらs2にできるかを返す関数。
def dist(s1, s2):
    different_cnt = 0
    for el1, el2 in zip(s1, s2):
        if el1 != el2:
            different_cnt += 1
    return different_cnt


N, M = map(int, input().split())
S = [input() for _ in range(N)]

ans = 'No'
for perm in permutations(range(N)):
    is_ok = True
    P = []
    for idx in perm:
        P.append(S[idx])
    for i in range(N - 1):
        if dist(P[i], P[i + 1]) > 1:
            is_ok = False
    if is_ok:
        ans = 'Yes'
print(ans)
```

# D - Impartial Gift
## 問題ページ
difficulty: <font color="brown">682</font>

https://atcoder.jp/contests/abc302/tasks/abc302_d

## 入力
$N$: 青木くんへの贈り物の個数
$M$: すぬけくんへの贈り物の個数
$D$: 2人への贈り物の価値の差を$D$以下にしたい。
$A_i$: $i$個目の青木くんへの贈り物の価値
$B_i$: $i$個目のすぬけくんへの贈り物の価値

## 考察
「貪欲法」と呼ばれるタイプの解き方をします。
あらかじめ、$A$と$B$をそれぞれ昇順にソートします。
$(P)$
$A$と$B$の中からそれぞれ最大値$a$と$b$を引っ張ってきます。
もし$|a-b|\leq D$ なら、この2つの贈り物は条件を満たしているし、$A$と$B$それぞれの最大値が$a$と$b$なので、これが答えです。
$|a-b|\gt D$ のとき、仮に$a$の方が大きいとします。思い返すと$b$は$B$の中で最も大きな値だったので、もう$B$の中には$a$との差の絶対値が$D$以下になる要素がありません。
つまり、この$a$をつかった贈り物のペアはつくれないことになります。
なので、$a$は捨ててしまって、残った$A$をつかって$(P)$からまた同じ処理をします。
$a$より$b$の方が大きかった場合も同じです。

## コード

```Python
N, M, D = map(int, input().split())
A = list(map(int, input().split()))
B = list(map(int, input().split()))

A.sort()
B.sort()
ans = -1

# A,Bからそれぞれ最大値を取って来て、差がD以下ならそれが答えになる。(貪欲法)
while len(A) > 0 and len(B) > 0:
    a = A.pop()
    b = B.pop()
    if abs(a - b) <= D:
        ans = a + b
        break
    # aとbで大きい方は、差がD以下になるような選び方ができない。
    # ⇒ 大きい方を捨てて、小さい方は元に戻す。
    if a < b:
        A.append(a)
    else:
        B.append(b)

print(ans)
```

# E - Isolation
## 問題ページ
difficulty: <font color="green">982</font>

https://atcoder.jp/contests/abc302/tasks/abc302_e

## 入力
$N$: 頂点の数
$Q$: とんでくるクエリの数

クエリ1
頂点$u$と頂点$v$を辺で結ぶ。このクエリの直前は頂点$u$と頂点$v$が辺で結ばれていない。

クエリ2
頂点$v$から出ている辺をすべて削除する(頂点$v$自体は削除しない)。


## 考察
$S_u$: 頂点$u$と辺で結ばれてる頂点を入れたset
になるリスト$S$を用意します。

クエリ1で頂点$u$と頂点$v$を結ぶときは、$S_u$に$v$を、$S_v$に$u$を追加します。
クエリ2で頂点$v$から出ている辺を削除するときは、$S_v$の中の頂点それぞれについて、その頂点を$u$としたとき$S_u$から$v$を削除します。その処理をすべて終えた後、$S_v$を空のsetにします。

最初はどの頂点も孤立してるから、変数$ans$を$N$にしておきます。
そこから各クエリを処理していきます。
今まで空のsetだったところに要素を追加したときや、要素が入っていたsetが空になったときに、$ans$を$1$増やしたり減らしたりします。


## コード

```Python
N, Q = map(int, input().split())

S = [set() for _ in range(N)]
ans = N

for _ in range(Q):
    query = list(map(int, input().split()))
    if query[0] == 1:
        u, v = query[1] - 1, query[2] - 1
        if len(S[u]) == 0:
            ans -= 1
        S[u].add(v)
        if len(S[v]) == 0:
            ans -= 1
        S[v].add(u)
    elif query[0] == 2:
        v = query[1] - 1
        for u in S[v]:
            S[u].discard(v)
            if len(S[u]) == 0:
                ans += 1
        if len(S[v]) > 0:
            ans += 1
        S[v] = set()
        
    print(ans)
```