AtCoder ABC390 振り返り感想戦(緑コーダーがPythonでABC問題)

## ABC 390感想まとめ

[AtCoder Beginner Contest 390 - AtCoder](https://atcoder.jp/contests/abc390)

今回はABC3問解答でした。D問題の難易度が高く、4問解答はハードルが高かったです。。。

B問題の等比数列かどうかを判定するところで1ペナになりました。入力例・出力例のところで小数の例があり、小数点の誤差の危険を感じてはいたのですけども、それでもまんまと引っかかってしまいましたね...。

コンテストでは、D問題は排他的論理和が入っていて難しそうなのでパスし、E問題に着手しました（実際に、D問題は青色レベルの問題で難しかった模様）。E問題も難易度が高く、なんとなく実装方針は立ったものの正解には及ばずでした。

## [A - 12435](https://atcoder.jp/contests/abc390/tasks/abc390_a)

素直に A の要素 i と i+1 を入れ替えて、判定しています。

``` python
A = list(map(int, input().split()))

for i in range(4):
    A2 = A.copy()

    # i と i+1 を入れ替える
    sw = A2[i]
    A2[i] = A2[i + 1]
    A2[i + 1] = sw

    # ソート済Aと一致判定
    if A2 == sorted(A):
        print("Yes")
        exit()
print("No")

```

## [B - Geometric Sequence](https://atcoder.jp/contests/abc390/tasks/abc390_b)

1ペナしてしまい悔しい問題。

理屈としては、「A[0]/A[1] の比率と A[i]/A[i+1] の比率が一致しているか」を、全探索で確認すれば良いのです。ただし、整数を割り算をすると答えが小数になってしまって、誤差が出てきたりします。

ですので、式変形して掛け算にします。

`A[0]/A[1] == A[i]/A[i+1]` → `A[0]*A[i+1] == A[i]*A[1]`

こうすると誤差が出ないわけですね...。

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

if len(A) == 2:
    print("Yes")
    exit()

for i in range(len(A) - 1):
    # 比率が一致しているか判定
    if A[0] * A[i+1] == A[i] * A[1]:
        continue
    else:
        print("No")
        exit()
print("Yes")
```

## [C - Paint to make a rectangle](https://atcoder.jp/contests/abc390/tasks/abc390_c)

まず、最も小さい黒の長方形は、どのようなサイズかを考えます。
黒マス(#)が確定しているマスは動かせないので、黒マス(#)のうち 一番上のマス、一番下のマス、一番左のマス、一番右のマスを含むような長方形が、最も小さい黒の長方形になります。

先ほど求めた最も小さい黒の長方形の範囲の中に、一個でも白マス(".")があるとNGになります。ですので、そのような判定を行えば ok　でした。


``` python
H, W = map(int, input().split())
field = [list(input()) for _ in range(H)]

# 黒マスのうち、最も上下左右の位置はどこかを記録する
top = H - 1
left = W - 1
right = 0
bottom = 0

# 黒マスのうち、最も上下左右の位置はどこかを記録する
for i in range(H):
    for j in range(W):
        if field[i][j] == "#":
            top = min(top, i)
            left = min(left, j)
            right = max(right, j)
            bottom = max(bottom, i)

# 先ほど求めた範囲の中に、一個でも白マス(".")があるとNG
for i in range(top, bottom + 1):
    for j in range(left, right + 1):
        if field[i][j] == ".":
            print("No")
            exit()

print("Yes")

```

## [E - Vitamin Balance](https://atcoder.jp/contests/abc390/tasks/abc390_e) (解説で復習)

コンテスト中は解けなかったので、解説を見て復習しました。

解き方は以下です。

手順1. 各ビタミン値とカロリー値の対応配列を作成する

ビタミン1〜3について、「カロリーが index のとき ビタミン値が value」の対応表を作成します。入力例1の場合、こんな感じ。

``` python
dp1=[0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]
dp2=[0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
dp3=[0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
```

単調増加なので、二分探索で「ビタミン値 v1 のときのカロリーは ？」がすぐに求められます。

手順2. 2分探索で、「ビタミン値=v2 のとき、カロリーはX以下に収まりますか？」 の上限を探す

この手の問題でよくある、二分探索する手法。手順1の結果を使って、ビタミン値-カロリー の対応がわかるので、これもすぐに求められます。

``` python
import bisect

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

AC1 = []
AC2 = []
AC3 = []
for i in range(N):
    V, A, C = map(int, input().split())
    if V == 1:
        AC1.append((A, C))
    elif V == 2:
        AC2.append((A, C))
    else:
        AC3.append((A, C))

# DPで indexがカロリー、valueがビタミン量の配列を作成する
def crateDP(AC):
    dp = [[0] * (X+1) for _ in range(len(AC)+1)]
    for i in range(len(AC)):
        a, c = AC[i]
        for j in range(1, X+1):
            if j - c >= 0:
                dp[i+1][j] = max(dp[i][j], dp[i][j-c] + a)
            else:
                dp[i+1][j] = dp[i][j]
    return dp[-1]

# 各ビタミンの カロリー - ビタミン量配列
dp1 = crateDP(AC1)
dp2 = crateDP(AC2)
dp3 = crateDP(AC3)

# ビタミン値が指定されたとき、カロリーXに収まるかを判定
def isEnable(value):
    v1index = bisect.bisect_left(dp1, value)
    v2index = bisect.bisect_left(dp2, value)
    v3index = bisect.bisect_left(dp3, value)
    enable = v1index + v2index + v3index <= X
    return enable

# 二分探索で最大スコアを求める
left = 0
right = min(max(dp1), max(dp2), max(dp3)) + 1

while right - left > 1:
    mid = (left + right) // 2
    if isEnable(mid):
        left = mid
    else:
        right = mid

print(left)
```