LoginSignup
1
0

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

Last updated at Posted at 2023-08-13

AtCoder Beginners Contest 314 (ABC314) をPythonで解きました。
見やすいコードを書く練習も兼ねてます。

A - 3.14

問題ページ

考察

問題文に円周率の小数点以下を書いてくれているので、それをコピペして文字列として受け取ります。
たとえば小数第2位までを聞かれたら"3.14"と答えます。文字列としてみると4文字です。
小数第 $N$ 位までを聞かれたら、答えは先頭 $N+2$ 文字になってます。

コード

S = "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"
N = int(input())
print(S[:N + 2])

B - Roulette

問題ページ

考察

出力する人の番号のリストansと、その人たちの書けていた番号の個数cntの2つの変数をつかいます。
人1から順番に見ていきます。
もし $X$ に賭けていなかったら、その人は対象外です。 $X$ に賭けていたかどうかはsetをつかって判定できます。
次にcntを見ます。もし賭けていた番号の個数がcntと同じなら、ansにその人の番号を追加します。 もし賭けていた番号の個数がcntよりも少ないなら、ansを初期化してからこの人の番号を追加してあげて、あとcntもこの人の賭けていた番号の個数に更新しておけばokです。

コード

N = int(input())
P = []
for _ in range(N):
    c = int(input())
    A = set(map(int, input().split()))
    P.append(A)

X = int(input())

ans = []
cnt = 1000
for i in range(N):
    if X in P[i]:
        if cnt == len(P[i]):
            ans.append(i + 1)
        elif cnt > len(P[i]):
            ans = [i + 1]
            cnt = len(P[i])

print(len(ans))
print(*ans)

C - Rotate Colored Subsequence

問題ページ

考察

色1で塗ったものだけをピックアップしてシフトして、色2で塗ったものだけをピックアップしてシフトして、...としたいです。
塗る色ごとに、「この色で塗ってるインデックスはどこかなー」と探していると $O(MN)$ の計算量でTLEしちゃいます。
これは、先に

$D_i$ : 色 $i$ で塗られたインデックスのリスト

になっている2次元リスト $D$ をつくってあげると解消できる問題です。

下のコードでは、答えとなる文字列 $T$ を別で用意してあげて、入力で受け取った文字列 $S$ は変更しないようにしています。こうしなくても解けるかもしれないんですが、個人的にこれがラクでこうしています。

コード

N, M = map(int, input().split())
S = list(input())
C = list(map(int, input().split()))

D = [[] for _ in range(M)]
for i, c in enumerate(C):
    D[c - 1].append(i)

T = S.copy()
for gr in D:
    if len(gr) <= 1:
        continue
    for i in range(len(gr)):
        prev_idx = gr[i]
        nex_idx = gr[(i + 1) % len(gr)]
        prev = S[prev_idx]
        T[nex_idx] = prev
print(*T, sep='')

D - LOWER

問題ページ

考察

$t=2$ や $t=3$ のクエリ(全部を小文字にしたり大文字にしたり)をクエリが飛んでくるごとに素直に処理していると、計算量 $O(NQ)$ でTLEしてしまいます。
$t=1$ のクエリは文字を1つ変えるだけでそんなに時間がかからなさそうです。問題になっているのは $t=2,3$ のクエリっぽいです。
では、最悪のパターンでクエリが $t=2, t=3, t=2, t=3,...$ と交互にとんできたときにどうするかを考えてみます。
結論から書くと、$t=2$ もしくは $t=3$ のクエリのうち、最後に来たものだけを採用すればいいです。それ以外のクエリは全部無視してしまって問題ないです(問題のサンプル1とサンプル2で実験してみると分かりやすいと思います)。

クエリがくるごとに処理せずに、先にすべてのクエリを受け取ってしまいます。このクエリたちの中で最後に来た $t=2$ もしくは $t=3$ がいつなのかを先に記録しておいて、それ以外の $t=2$ もしくは $t=3$ のクエリは全部無視すればokです。(「クエリ先読み」と呼ばれる頻出テクニックです)

コード

N = int(input())
S = list(input())

last_idx = -1

Q = int(input())
query = []
for qi in range(Q):
    t, x, c = input().split()
    t, x = int(t), int(x) - 1
    query.append((t, x, c))
    if t == 2 or t == 3:
        last_idx = qi

for qi in range(Q):
    t, x, c = query[qi]
    if qi == last_idx:
        if t == 3:
            for i in range(N):
                S[i] = S[i].upper()
        elif t == 2:
            for i in range(N):
                S[i] = S[i].lower()
        continue
    if t == 1:
        S[x] = c

print(*S, sep='')

E - Roulettes

問題ページ

考察

期待値DPと呼ばれる類の問題です。
次のリストを用意します。

dp[i]: iポイントを持っていて、Mポイント以上にするために必要な金額の期待値

このリストを末尾から順番に完成させていきます。期待値DPは後ろから解いてみる、というのは典型です。(例: EDPC-J Sushi)

具体的には、たとえばサンプル1を解いていて、 $dp[5]$ ~ $dp[M]$ は分かっていて $dp[4]$ を求めたいとします。
どのルーレットを使うのが最適かは分からないので、全部試してみて、一番期待値のよかったルーレットを採用することにします。
まずコスト $100$ 円で目が $(5,9)$ のルーレットを使ってみます。$5$ の目が出たら $4+5=9$ ポイントになり、 $9$ の目が出たら $4+9=13$ ポイントになります。なので、このルーレットをつかうと、$dp[4]=100+(dp[9]+dp[13])/2$ 円になります。
これと同じことを他のルーレットでもすべてやってみて、支払う金額の期待値が最も低かったものを採用すればokです。

また、 $0$ が含まれているルーレットに関しては、たとえばサンプル2だと $(0,0,0,0,0,100)$ のルーレットがありますが、$100$ の目が出る確率は $1/6$ で、 $100$ の目を出すために必要なルーレットを回す回数の期待値は $6$ 回です。
一般的に、確率 $p$ で起こる事象があったとして、その事象を起こすのに必要な回数の期待値は $1/p$ になります。($1$ %しかあたらないガチャを当てるのに、平均で $100$ 回ガチャを回さないといけないね、的なことです)
$0$ の目が含まれるすべてのルーレットについて、$0$ の目をなくしてその分だけ代わりにコストを増やしておきます。たとえば先ほどの $(0,0,0,0,0,100)$ でコスト $10$ 円のルーレットなら、 $(100)$ のルーレットに変換して、 $0$ でない目が出る確率は $1/6$ だったので、コストを $6$ 倍して $60$ 円にします。

コード

N, M = map(int, input().split())
R = []
C = []
for _ in range(N):
    c, p, *s = map(int, input().split())
    zero_cnt = s.count(0)
    s.sort()
    if zero_cnt:
        c *= len(s) / (len(s) - zero_cnt)
        s = s[zero_cnt:]
    C.append(c)
    R.append(s)

# dp[i]: iポイントを持っていて、Mポイント以上にするために必要なコインの期待値
dp = [1 << 80] * (M + 1)
dp[M] = 0

for i in range(M - 1, -1, -1):
    for j in range(N):
        now_val = 0
        cost = C[j]
        rou = R[j]
        p = 1 / len(rou)
        for el in rou:
            nex = min(M, el + i)
            now_val += (dp[nex] + cost) * p
        dp[i] = min(dp[i], now_val)
print(dp[0])

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0