【AtCoder】ABC297のA,B,C,D,E問題をpythonで解説

ABC

ABC297のA,B,C,D,E問題をpythonで解説していきます。

A – Double Click

入力
N : クリック回数
D : ダブルクリックが成功したかの基準の時間
A[i] : i回目にクリックした時刻

考察

N回の操作のうち、i回目のタイムとi-1回目のタイムの差がT秒以下だったら試行i回目のタイムを出力すれば良いです。

そのため、2回目からN回目の操作において、現在の時刻と1つ前の時刻の差分を取り、Dと比べれることで答えにたどり着けます。

コード

N,D = map(int, input().split())
T = list(map(int, input().split()))

for i in range(1,N):#2回目からN回目の操作において
    if T[i]-T[i-1] <=D:#i回目とi-1回目の時間の差がD以下であれば
        print(T[i])
        exit()
print(-1)#ダブルクリックが一度も行われていなければ

B – chess960

入力
S : 長さ8の文字列

考察

今回の問題では、

  • Bの偶奇が異なること
  • Kと2つのRの位置関係がRKRのようにKがRに挟まれていること

の2条件が挙げられます。

1つ目の条件については、2つのBの位置の和が奇数なら条件を満たします
(偶数+偶数=偶数,奇数+奇数=偶数のため)

2つ目の条件については、Kの座標をintで取得し、と2つのRの座標をリストで取得し、Kを挿入する際のインデックスが1であることを確かめれば良いです。

ind   0 1 2
list [R K R]

コード

s = input()

check =0#Bの偶奇をチェックする
for i in range(8):
    if s[i] == "B":
        check += i
if check %2 == 0:
    print("No")
    exit()

king = 100#後に更新される 適当な数字
R = []
for i in range(8):
    if s[i] == "K":
        king = i#Kの座標を取得
    if s[i] == "R":#Rの座標を取得
        R.append(i)

import bisect

a = bisect.bisect_left(R,king)#kingを挿入する際に何番目に挿入されるか
if a == 1:#挿入される場所が1なら
    print("Yes")
else:
    print("No")

C – PC on the Table

入力
H:縦の長さ
W:横の長さ
S[i]: 文字列Sのi番目の要素

考察

今回重要なのは、

  • 操作は行方向に行っていく
  • 任意のx行目に操作を行っても、別の行には影響は出ない

ということです。そのため、1行目からH行目まで独立して操作を行う事ができます。

また今回は、制約がH,W < 100なので、全探索することが可能です。よって具体的な操作としては、任意の行(x行目)の1-W列目において

  • もし、自分(j)がTで、自分の右隣(j+1)もTであれば、自分と右隣をそれぞれP,Cに変更する

その結果を出力することで答えにたどり着けます。

コード

H,W = map(int, input().split())
box = []#最後に答えを出力する空のリスト
cnt = 0
for i in range(H):
    s = input()#文字列を受け取る
    s_l = list(s)#文字列だと操作しづらいのでリストに変換
    for j in range(W-1):
        if s_l[j]== s_l[j+1] == "T":#もし、j番目もj+1番目もTだったら
            s_l[j] = "P"#j番目をPに
            s_l[j+1] ="C"#j+1番目をCへ
    
    ans = ""#答えを出力する空の文字列
    for j in range(W):#先程変更した文字列を
        ans += s_l[j]#順に連結(足して)
    box.append(ans)#最終的な答えのリストに格納

for i in range(H):
    print(box[i])

D – Count Subtractions

入力
A,B : 正の整数

考察

まずはじめにA==Bのときは答えは0です。以降はA≠Bのときについてです。

今回愚直にwhile文でやるとTLEします。なので、どうにかして高速化をする必要があります。

そこで、A,Bが最終的にどのような数になるかを考えます。

ex1) 3, 8
→3, 5
→3, 2
→1, 2
→1, 1
ex1) 3, 9
→3, 6
→3, 3

上記2つの例から、A,Bは最大公約数に帰着しそうです。

なぜなら、「AとBに共通する約数(Z)がある場合、abs(A-B)も必ずZを約数に持つため」です。

ex2)
27と9の最大公約数→9
27-9 = 18 →18は9を約数に持つ(18は9の倍数である)

このことから、A,Bともに最大公約数に変遷するまでの操作回数を求めることで問題を解くことが可能です。

変遷する回数は

  1. AがBの倍数の時(A%B == 0)
    A/B-1回です。
    A/Bは元の数字から変遷した数字の個数(27→18→9)
    -1 は矢印の回数(3-1=2)で求められるため
  2. AがBの倍数ではないとき((A%B != 0)
    AがBよりも小さな値になるまでBで引き続けます。その場合の操作回数(cnt)はA//Bです(cnt == A//BよりB x cnt >= Aなので)

の2パターンに分けることができ、もしパターン2でAの値が更新されたら、繰り返し1か2の処理を行います。

上記の操作を繰り返すことで、高速にA==Bとなる回数を求めることができます

コード

import math
A,B = map(int, input().split())
# print(math.gcd(A,B))
if A ==B:#もしAとBが同じ値なら
    print(0)
    exit()
cnt = 0
#以降A≠Bについて
while A != B:
    #Aのほうが大きいようにしたいので、
    if A < B:#もしAよりもBが大きかったら
        A,B = B,A#AとBを入れ替える
    
    if A%B == 0:#もしAがBの倍数なら
        cnt+= A//B-1#変遷した数字の数-1=変遷した回数
        print(cnt)
        exit()
    else:#AがBの倍数ではないなら
        cnt += A//B#AがBよりも小さな値になるまで変遷させる
        A = A%B#新しいAがの値

E – Kth Takoyaki Set

入力
N:たこ焼きの種類
K:何番目に安い支払いを出力するべきか?
A[i]:i番目のたこ焼きの値段

考察

結論から書きますと、今回動的計画法(dp)を用いて解く問題です。
(制約からN*Kのdpぽいなって思いました)

なぜなら、i番目の商品を買うか買わないか(買った場合i番目に安い支払いに影響がでるか出ないか)は、「i-1番目までの買い物の結果と、i番目の商品を何個買うか」の2つの要素のみで決まるからです。

ex)
4 6
20 25 30 100

商品1しかない場合

支払いのパターンは

20,40,60,80,100,120 となる

商品2もある場合

商品1の支払い方法[20,40,60,80,100,120]と商品2の[25]を使って

20,25,40,45(20+25),50(25+25),60となる

商品3もある場合

商品2までの最適な支払い方法[20,25,40,50,60,65]と商品3の[30]を使って・・・

これまで支払った値と現在の商品の値だけを用いることでK番目に最小の支払い方法がわかります

そのため、縦軸に商品の数(N)、横軸に支払いのパターン(K)を取り、動的計画法を用いることで答えにたどり着けます。

コード

N,K = map(int, input().split())
A = list(map(int, input().split()))
A.sort(reverse=True)#商品の値段を大きいもの順に並べる
# 上記操作はなくても大丈夫なはずだが、理解しやすくするために並べ替えた

dp = [[0]* (K+1) for i in range(N)]#N+(K+1)のdpを作成

for j in range(1,K+1):#商品2のみを使用したK番目までの支払い方法
    dp[0][j] = A[0]*j

for i in range(1,N):
    is_flg = 0#次参照する商品iまでを使用した支払い方法
    was_flg = 1#参照する商品i-1までを使用した支払い方法
    for j in range(1,K+1):
        if dp[i][is_flg]+A[i] == dp[i-1][was_flg]:
            #商品iを買って、i-1番目までのj番目の支払いの金額と同じ担った場合
            dp[i][j] = dp[i][is_flg]+A[i]#どちらの値を使用してもよい
            is_flg += 1#次参照する商品iまでを使用した支払い方法を更新
            was_flg += 1#次参照する商品i-1までを使用した支払い方法を更新
        elif dp[i][is_flg]+A[i] < dp[i-1][was_flg]:#商品iを買ったら、i-1番目までのj番目の支払いよりも小さい時
            dp[i][j] = dp[i][is_flg]+A[i]
            #商品iまで使用して支払うj番目の支払い=商品iまでを支払うパターンにおいてis_flg番目に安い支払いの値+i番目の商品の値
            is_flg += 1#次参照する商品iまでを使用した支払い方法のみを更新
        elif dp[i][is_flg] +A[i]> dp[i-1][was_flg]:#商品iを買ったら、i-1番目までのj番目の支払いよりも大きい時
            dp[i][j] = dp[i-1][was_flg]
            #商品iまで使用して支払うj番目の支払い=商品i-1までを支払うパターンにおいてwas_flg番目に安い支払いの値
            was_flg += 1#次参照する商品i-1までを使用した支払い方法のみを更新

print(dp[-1][K])

コメント

タイトルとURLをコピーしました