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

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

# A - Full Moon

## 問題ページ
https://atcoder.jp/contests/abc318/tasks/abc318_a

## 考察
変数 $ans$ を用意して、最初は $ans=0$ とします。
$M$ 日目、 $M+P$ 日目、 $M+2P$ 日目、$\cdots$ と見ていきます。
もし今見ている日にちが $N$ 以下のときは、変数 $ans$ を $1$ だけ増やして、次の日にちを見に行きます。
もし今見ている日にちが $N$ を超えていたら、日にちを $P$ ずつ増やしていた操作をbreak文などで打ち切って、変数 $ans$ を出力します。
下のコードはwhile文をつかっています。別解にもその他の書き方があるので、参考にしてみてください。

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

ans = 0
while M <= N:
    ans += 1
    M += P
print(ans)
```

## 別解
<details>  <summary>for文をつかった解法</summary>
for文をつかって、 $M$ 日目、 $M+P$ 日目、　$M+2P$ 日目、 $\cdots$ と日付を見ていきます。
rangeの引数には、どうやっても見ることのないくらい大きな数字を入れておき、見ている日にちが $N$ を超えた時点でbreak文をつかってこのループ処理を打ち切ります。

```Python
# for文をつかった解法
N, M, P = map(int, input().split())
ans = 0
for i in range(5000000):
    day = M + P * i
    if day <= N:
        ans += 1
    else:
        break
print(ans)
```

</details>

<details>  <summary>range関数の第3引数をつかった解法</summary>

range関数は、たとえば「 $3$ から $14$ までの整数を $3$ 個とばしで見る」みたいなことができます。

指定するのは $3$ つで、「スタートの数」「おわりの数」「何個とばしでみるか」です。「おわりの数」は、本来の数から $1$ だけ足しておかないといけません。

つかいかたはこんな感じ。

```Python
# 3から14までの整数を3個とばしで見る。
for i in range(3, 15, 3):
    print(i)  # 3, 6, 9, 12 が順に出力される

# 5から22までの整数を4個とばしで見る。
for i in range(5, 23, 4):
    print(i)  # 5, 9, 13, 17, 21 が順に出力される
```

この性質を用いて、今回の問題を解くことができます。

```Python
# range関数の第3引数をつかった解法
N, M, P = map(int, input().split())
ans = 0
for day in range(M, N + 1, P):
    ans += 1
print(ans)
```

</details>

<details>  <summary>len関数をつかった解法(発展)</summary>
上の「range関数の第3引数をつかった解法」の発展バージョンです。  

今回の問題では、何回ループするかの情報がほしいだけです。これは実際にfor文をつかってループさせなくても、len関数をつかって取得できます。

```Python
# len関数をつかった解法
N, M, P = map(int, input().split())
ans = len(range(M, N + 1, P))
print(ans)
```

</details>

<details>  <summary>こっちで計算する解法(発展)</summary>
for文やwhile文をつかってPythonにループ処理を頑張ってもらってもいいですが、自分で計算することもできます。
スタートの日にちが入力で与えられる $M$ によって毎回変わってしまうので、最初の日にちを $0$ 日目で統一するために、次のような変更をします。  

- 満月を見られる最初の日は $M$ 日目 → 満月を見られる最初の日は $0$ 日目
- $1$ 日目から $N$ 日目の中で、満月を見られる日の数を求める →  $0$ 日目から $N-M$ 日目の中で、満月を見られる日の数を求める

本来は今日がスタートだったのを $M$ 日後の今日に置き換えて、 1-indexedを0-indexedになおしました。
たとえば、「満月は $5$ 日ごとに現れる( $P=5$ )とき、 $0$ 日目から $28$ 日目の中で、満月を見られる日の数を求めてください。ただし、 $0$ 日目に満月が見られるとします。」という問題のとき、満月が見られるのは $0$ 日目、 $5$ 日目、 $10$ 日目 、 $15$ 日目、 $20$ 日目、 $25$ 日目の $6$ 回です。これは、$28//5 + 1=6$ として求めることもできます(//は小数点以下を切り捨てる割り算)。

これをそのままコードにすることで解くことができます。
$N<M$ のときは答えが $0$ になるので注意しましょう。

```Python
# こっちで計算する解法
N, M, P = map(int, input().split())
ans = (N - M) // P + 1
if ans < 0:
    ans = 0
print(ans)
```

</details>


# B - Overlapping sheets

## 問題ページ
https://atcoder.jp/contests/abc318/tasks/abc318_b

## 考察
$101 \times 101$ のマスを用意して、シートで覆った部分はTrue、そうでない部分はFalseということにします。
$N$ 枚のシートが与えられるので、それぞれのシートについて、覆った部分をTrueにしていきます。
$101 \times 101$ のマスの中にあるTrueの数がそのまま答えになります。

## コード
```Python
N = int(input())
Grid = [[False] * 101 for _ in range(101)]
for _ in range(N):
    a, b, c, d = map(int, input().split())
    for i in range(a, b):
        for j in range(c, d):
            Grid[i][j] = True
ans = 0
for i in range(101):
    for j in range(101):
        if Grid[i][j]: ans += 1
print(ans)
```
# C - Blue Spring

## 問題ページ
https://atcoder.jp/contests/abc318/tasks/abc318_c

## 考察
1日周遊パスはどのタイミングでつかってもいいです。
また気持ち的には、通常料金の高い日に1日周遊パスをつかって、通常料金の安い日は1日周遊パスはつかわないようにしたいです。

なので、リスト $F$ で与えられる各日にちの通常料金のリストを、大きい順(降順)にソートします。サンプル1だと、$F=[7,1,6,3,6]$ だったのが、 $F=[7,6,6,3,1]$ になります。

最初の $D$ 日間を見てみます。
もし最初の $D$ 日間は1日周遊パスをつかった方がいいなら、$D$ 枚セットの1日周遊パスを買って、最初の $D$ 日間につかうことにします。
サンプル1だと、 $F=[7,6,6,3,1]$ で、1日周遊パスは $2$ 枚セットで $10$ 円です。最初の $2$ 日間は何もしないと $13$ 円かかってしまうので、$2$ 枚セットの1日周遊パスを $10$ 円で購入します。
これを次の $D$ 日間、さらに次の $D$ 日間、 $\cdots$ と繰り返していき、実際にかかるお金がいくらかを計算すればokです。

下のコードでは、 $D$ 枚セットの1日周遊パスを買ってつかうとき、 $D$ 日間のうち最初の日だけ $P$ 円(=1日周遊パスの値段)、それ以外の日は $0$ 円としています。


## コード
```Python
N, D, P = map(int, input().split())
F = list(map(int, input().split()))
F.sort(reverse=True)

for left in range(0, N, D):
    right = min(left + D, N)
    cost = sum(F[left:right])
    if cost > P:
        for i in range(left, right):
            F[i] = 0
        F[left] = P
    else:
        break
ans = sum(F)
print(ans)
```
# D - General Weighted Max Matching

## 問題ページ
https://atcoder.jp/contests/abc318/tasks/abc318_d

## 考察
考えうる辺の結び方は、 $15 \times 13 \times 11 \times 9 \times 7 \times 5 \times 3 = 2027025$　通りで、これなら全探索できそうです。
DFS全探索で解きます。

再帰関数をつかうので、次の4行を書いておくようにしましょう。
```Python
import sys
import pypyjit

sys.setrecursionlimit(10**7)
pypyjit.set_param('max_unroll_recursion=-1')
```

$N$ が奇数のとき、浮いちゃう頂点が出てきてややこしいので、頂点を1つ増やしてその頂点から出ている辺はすべて重みを $0$ とします。

- $lst[i]$ : 辺の番号が2つ格納されたリスト

となるリスト $lst$ を全パターンつくります。
以下、どのようにつくるかを書いていきます。

格納する辺の番号を $v$ 、つくってる途中のリストを $lst$ とします。このことを $dfs(lst,v)$
 と表記します。
$lst$ を左から順に見ていきます。
要素数が $2$ のとき、その2頂点をつなぐ予定なので、そこには $v$ は入れません。
要素数が $0$ のとき、そこに $v$ を入れた新たなリスト $nexlst$ を用意し、$dfs(nexlst, v+1)$ を考えることにします。 ただし、もう要素数が $0$ のところに $v$ は入れません。
要素数が $1$ のとき、そこに $v$ を入れた新たなリスト $nexlst$ を用意し、$dfs(nexlst, v+1)$ を考えることにします。

これで全パターンの $lst$ を列挙することができます。



## コード
```Python
import sys
import pypyjit

inputs = sys.stdin.readline
sys.setrecursionlimit(10 ** 7)
pypyjit.set_param('max_unroll_recursion=-1')

N = int(inputs())
D = []
for i in range(N - 1):
    row = list(map(int, inputs().split()))
    D.append([0] * (i + 1) + row)
D.append([0] * N)
for i in range(N):
    for j in range(N):
        if i != j and D[i][j] == 0:
            D[i][j] = D[j][i]

if N % 2 == 1:
    for i in range(N):
        D[i].append(0)
    D.append([0] * (N + 1))
    N += 1

ans = 0


def dfs(lst, v):
    if v == N:
        global ans
        now_ans = 0
        for u, v in lst:
            now_ans += D[u][v]
        ans = max(ans, now_ans)
        return

    append_to_zero = False  # 過去に要素数0のところにvを追加したことがあればTrue
    for i in range(len(lst)):
        match len(lst[i]):
            case 2:
                continue
            case 0:
                if append_to_zero:
                    break
                append_to_zero = True
        nex_lst = [t[:] for t in lst]

        nex_lst[i].append(v)
        dfs(nex_lst, v + 1)


dfs([[] for _ in range(N // 2)], 0)
print(ans)
```

## 別解

<details>  <summary>bitDPをつかった解法</summary>
$0$ ～ $2^N - 1$ の合計 $2^N$ 個の数を用意します。これらの数は、「2進数で表したときに下から $i$ 桁目が $1$ のとき、頂点 $i$ はすでに他の頂点と結ばれている」という状態を表すことにします。

- $dp[S]$ : グラフの状態が $S$ のときの、辺の重みの総和の最大値

となるdpを考えます。
最初は $dp[0] = 0$ です。

以下、下のコードで読み解くのがしんどくなりそうなところを解説します。

グラフの状態を表す整数 $S$ について、頂点 $i$ がすでに他の頂点と結ばれているかどうかは、次のように判定できます。
```Python
if (S >> i) & 1 == 1:
    print('頂点iは他の頂点と結ばれている')
elif (S >> i) & 1 == 0:
    print('頂点iはどの頂点とも結ばれていない')
```

また、もとのグラフの状態が $S$ で、そこに頂点 $a,b$ を結ぶ辺を追加するのは、次のように書けます。
```Python
S |= 1 << a
S |= 1 << b
```

itertools.combinations()をつかうと、あるリストから組み合わせのパターンをすべて列挙することができます。
```Python
from itertools import combinations

lst = [5, 12, 3, -92]
for a, b in combinations(lst, 2):
    print(a, b)

# 出力結果
# 5 12
# 5 3
# 5 -92
# 12 3
# 12 -92
# 3 -92
```



```Python
# bitDPをつかった解法
from collections import deque
from itertools import combinations

N = int(input())
D = []
for i in range(N - 1):
    row = list(map(int, input().split()))
    D.append([0] * (i + 1) + row)
D.append([0] * N)
for i in range(N):
    for j in range(N):
        if i != j and D[i][j] == 0:
            D[i][j] = D[j][i]

dp = [0] * (1 << N)
que = deque()
que.append(0)
while que:
    state = que.popleft()
    P = [i for i in range(N) if (state >> i) & 1 == 0]  # つかえる頂点のリスト
    if len(P) >= 2:
        for a, b in combinations(P, 2):
            nex_state = state
            nex_state |= 1 << a
            nex_state |= 1 << b
            if dp[nex_state] == 0:
                que.append(nex_state)
            dp[nex_state] = max(dp[nex_state], dp[state] + D[a][b])
print(max(dp))
```

</details>

# E - Sandwiches

## 問題ページ
https://atcoder.jp/contests/abc318/tasks/abc318_e

## 考察 (パート1)
次の問題を考えます。
> 長さ $N$ の整数列 $A=(A_1,A_2, \cdots ,A_N)$ が与えられます。以下の条件をすべて満たす正整数組 $(i,j,k)$ の個数を求めてください。
> - $1 \leq i < j < k \leq N$
> - $A_i = A_k$

もとの問題文から、$A_j \neq A_k$ の条件を取り除いただけです。この問題をいったん解いてみます。

サンプル3の $A=(9, 7, 11, 7, 3, 8, 1, 13, 11, 11, 11, 6, 13)$ を考えてみます。
たとえば $k=10$ (右から $4$ 番目)のときを考えます。
$A_i=A_{10}$ となる $i$ は、 $i=3,9$ です。
$(i,k)=(3,10)$ のとき、$j=4,5,6,7,8,9$ で $6$ 通りです。これは、 $k-i-1=6$ で算出できます。
$(i,k)=(9,10)$ のときは、$0$ 通りです。

次に $k=11$ のときを考えます。
$A_i=A_{11}$ となる $i$ は、 $i=3,9,10$ です。
$(i,k)=(3,11)$ のとき、 $k-i-1=7$ で $7$ 通りです。
$(i,k)=(9,11)$ のとき、 $k-i-1=1$ で $1$ 通りです。
$(i,k)=(10,11)$ のときは、$0$ 通りです。

今回はもう値が $11$ の場所がこの右側にないですが、もし右側にまだまだ $11$ の値があったら、そのたびに $3$ や $9$、 $10$ を $k$ から引くことになってしまいます。
毎回計算しなくても、 $k \times (左側で11の出た回数) - (11が出てきたインデックスの総和)$ で求められます。
この計算をするときに必要なのは、今までに左側で何回 $11$ が出てきたか、$11$ が出てきたインデックスの総和、の2つだけです。
なので、次の2つのリストを用意します。

- $cnt[i]$ : 数字 $i$ が登場した回数
- $value[i]$ : 数字 $i$ が登場したインデックスの総和

この2つのリストを管理しながら、$k=1$ から $k=N$ まで計算していきます。
これで、最初につくった問題を解くことができました。

## 考察 (パート2)
次の問題を考えます。

> 長さ $N$ の整数列 $A=(A_1,A_2, \cdots ,A_N)$ が与えられます。以下の条件をすべて満たす正整数組 $(i,j,k)$ の個数を求めてください。
> - $1 \leq i < j < k \leq N$
> - $A_i = A_k$
> - $A_j = A_k$

もとの問題文の$A_j \neq A_k$ の条件を $A_j=A_k$ に変えました。
パート1での問題の答えから、この問題の答えを引くことで、もとの問題の答えになります(補集合の考え方です)。

$A_i$ と同じ値の数を $c$ とすると、その数について $A_i=A_j=A_k$ になるパターンの数は、$_cC_3=c(c-1)(c-2)/6$ です。

これで、もとの問題を解くことができました。

## コード
```Python
N = int(input())
A = list(map(lambda a: int(a) - 1, input().split()))

ans = 0
cnt = [0] * N  
value = [0] * N
for i, a in enumerate(A):
    c = cnt[a]
    v = value[a]
    if c >= 1:
        ans += i * c - v - c
    cnt[a] += 1
    value[a] += i

for c in cnt:
    if c <= 2:
        continue
    ans -= c * (c - 1) * (c - 2) // 6

print(ans)
```