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

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

# A - Echo

## 問題ページ
https://atcoder.jp/contests/abc306/tasks/abc306_a

## 考察
文字を1つずつ見ていって、その文字を2回出力します。
Pythonのprint関数はデフォルトだと改行が入ってしまうので、おまじないで第2引数にend=''とつけてあげます。
<details><summary>print関数のendについて</summary>
先に結論から書くと、endのところには出力した後の末尾を書きます。
たとえばこんな感じ。

```Python
# 末尾にxをつけて出力するならこう。
for i in range(4):
    print(i, end='x')  # 0x1x2x3x

# よくあるのは下の2つ。
# 改行なし、全部ぴったりくっつけて出力
for i in range(4):
    print(i, end='')  # 0123

# 改行なし、半角スペースをあけて出力
for j in range(4):
    print(i, end=' ')  # 0 1 2 3
```

</details>

## コード
```Python
N = int(input())
S = input()
for s in S:
    print(s + s, end='')
```
# B - Base2

## 問題ページ
https://atcoder.jp/contests/abc306/tasks/abc306_b

## 考察1
2進数で考えます。
たとえばサンプル1は答えが$13$になっていて、2進数表記すると、$13=(1101)_2$ です。
$A_0$が1の位、$A_1$が10の位、...となっていて、普段見ている数字と左右が逆になっているのに注意します。
リストAの左右を反転させて、それを2進数だと思って10進数表記に直してあげればACです。

## コード1
```Python
A = list(input().split())
A = A[::-1]   # 左右の反転でよくつかいます！
N = ''.join(A)  # リストを1つの文字列にする。
print(int(N, 2))  # 2進数の文字列を10進数の数に変換する。
```

## 考察2
こっちの方が簡単で、問題文の通りに式を立てます。
(Pythonだとオーバーフローが起こらなくて、これはPython有利(?)な問題だと思います！)

## コード2
```Python
A = list(map(int,input().split()))

ans = 0
for i in range(64):
    ans += A[i] * (2 ** i) 
print(ans)
```


# C - Centers

## 問題ページ
https://atcoder.jp/contests/abc306/tasks/abc306_c

## 考察
それぞれの数字について、何番目に現れたのかを記録します。
具体的には、$A[i]=num$のときリスト$G[num]$に$i$を格納します。
同じ数字は3回出てくるらしいので、どの$i$についても$G[i]$の中には3つしか数字が入っていません。
問題文で出てくる$f(i)$は、$G[i][1]$ のことになります。
$f(i)$の小さいものから順に$i$を出力すればいいので、リストPを用意して、その中に$(f(i), i)$のタプルを格納していって、最後にPを昇順にソートすればいいです。

## コード
```Python
N = int(input())
A = list(map(int, input().split()))

G = [[] for _ in range(N)]

for i, a in enumerate(A):
    G[a - 1].append(i)

P = []
for i in range(N):
    P.append((G[i][1], i + 1))
P.sort()
for a, b in P:
    print(b, end=' ')
```
# D - Poisonous Full-Course

## 問題ページ
https://atcoder.jp/contests/abc306/tasks/abc306_d

## 考察
動的計画法DPで解きます。次のように2次元リストdpを決めます。
$dp[i][flag]$: $i$個目までの品物が出てきて、高橋くんの状態がflag(0なら元気、1なら腹痛)のときの、食べた料理のおいしさの総和
初期状態は  $dp[0][0]=0$ です。

先に出された料理を食べないときを考えます。次のように遷移します。
$dp[i+1][0]=dp[i][0]$
$dp[i+1][1]=dp[i][1]$

次に出された料理を食べる時を考えます(おいしさを$v$とします)。
料理に毒が盛られていないとき、
$dp[i+1][0]=max(dp[i+1][0], dp[i][0]+v, dp[i][1]+v)$
料理に毒が盛られているとき、腹痛だと食べられないことに注意します。
$dp[i+1][1]=max(dp[i+1][1], dp[i][0]+v)$

答えは$max(dp[N-1][0], dp[N-1][1])$です。



## コード
```Python
INF = 1 << 80

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

# dp[i][flag]: max_score
# flag=0: 元気、 flag=1: やばい
dp = [[-INF] * 2 for _ in range(N + 1)]
dp[0][0] = 0

for i in range(N):
    dp[i + 1] = dp[i][:]
    x, v = P[i]
    if x == 0:
        dp[i + 1][0] = max(max(dp[i]) + v, dp[i + 1][0])
    else:
        dp[i + 1][1] = max(dp[i][0] + v, dp[i + 1][1])

print(max(dp[-1]))
```
# E - Best Performances

## 問題ページ
https://atcoder.jp/contests/abc306/tasks/abc306_e

## 考察
tatyamさん作のSortedMultiSetをつかいます(いつもありがとうございます！)。
大きい方から上位k個を入れておくSortedMultiSetと、それ以外のSortedMultiSetをつくります。
また、1つ目のSortedMultiSetに入ってる数の総和を表す変数totも用意しておきます。

あとは実際にシミュレートするのみです。
シミュレートするのに上の3つが必要だということが分かりますか？というところが1つ目の壁で、これを時間内にコードにできますか？というのが2つ目の壁な気がします。

どうシミュレートするかを書くとかなり長くなるので、詳しくはコードを見てください。


## コード
```Python
'''
tatyamさん作の、SortedMultisetです。
使わせていただき、ありがとうございます！
https://github.com/tatyam-prime/SortedSet/blob/main/SortedMultiset.py

SortedSetの多重集合版。同じ要素を複数入れることができる。
SortedSetから、s.add(x), s.discard(x) が変更され、s.count(x)が追加されている。

・使い方(個人的まとめ)
s=SortedMultiSet()
s.a: SortedMultiSetの中身を返す。
len(s), x in s, x not in s: リストと同じ要領で使える。
s.add(x): xを追加する。sにxが含まれているかどうかは関係ない。
s.discard(x): xを1つだけ削除してTrueを返す。もし存在しないなら、Falseを返す。
s.count(x): sに含まれるxの個数を返す。
s.lt(x): xより小さい最大の要素を返す。もし存在しないなら、Noneを返す。
s.le(x): x　以下の　最大の要素を返す。もし存在しないなら、Noneを返す。
s.gt(x): xより大きい最小の要素を返す。もし存在しないなら、Noneを返す。
s.ge(x): x　以上の　最小の要素を返す。もし存在しないなら、Noneを返す。
s.index(x): xより小さい要素の数を返す。
s.index_right(x): x以下の要素の数を返す。

・使い方URL
https://github.com/tatyam-prime/SortedSet
'''
# (SortedMultiSetが長いから省略。上のURLからコピペしてね。)

N, K, Q = map(int, input().split())

A = [0] * N
use_set = SortedMultiset()
for _ in range(K):
    use_set.add(0)
gar_set = SortedMultiset()  # garbage
for _ in range(N - K):
    gar_set.add(0)
tot = 0

for _ in range(Q):
    x, y = map(int, input().split())
    prev = A[x - 1]
    A[x - 1] = y
    c = gar_set.discard(prev)

    # gar_setからprevを取り出しているとき
    if c:
        if use_set.lt(y) is not None:
            el = use_set.ge(-1)
            use_set.discard(el)
            gar_set.add(el)
            tot -= el
            use_set.add(y)
            tot += y
        else:
            gar_set.add(y)

    # gar_set内にprevがなかったとき
    else:
        use_set.discard(prev)
        tot -= prev
        if use_set.le(y) is None:
            gar_set.add(y)
            el = gar_set.le(1000000007)
            gar_set.discard(el)
            use_set.add(el)
            tot += el
        else:
            use_set.add(y)
            tot += y
    print(tot)

```