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

ABC

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

拙い文章で分かりづらい部分がありましたら、@EitaSugiご連絡いただけると幸いです!!

A – Attack

問題

考察

A.1 考えた内容の概要

敵の体力を減らすためには攻撃が必要で、その攻撃が一定の値(B)だけ敵の体力を減らすことができます。
そのため、敵の体力を0以下にするためには、敵の体力(A)を一定の値(B)で何回割り切れるかを考える問題となります。

A.2 考えたことの内容

問題は、基本的に除算の問題とみなすことができます。敵の体力(A)を攻撃の力(B)で割ると、何回攻撃する必要があるかが求まります。

しかしながら、体力(A)が攻撃の力(B)で割り切れない場合、余りが出てしまうため、さらに一回攻撃する必要があります
よって、割り算の結果と余りに基づいて攻撃回数を計算するという手法を用います

A.3 実装した内容

Pythonの整数除算(//)を利用して攻撃回数を求めます。
整数除算は除算の結果を整数で返すため、体力(A)が攻撃力(B)で割り切れる場合はその結果が最小の攻撃回数となります。

一方、割り切れない場合、つまり余りがある場合は、さらに一回の攻撃が必要になります。そのため、余りがあるかどうかを調べるためにモジュロ演算(%)を使用しました。

コード

# 敵の体力 A と攻撃の力 B を整数として入力を受け取る
A,B = map(int, input().split())

# A を B で割った余りが 0 ならば、つまり A が B で割り切れるならば
if A%B == 0:
    # A を B で割った結果(整数除算)が最小の攻撃回数
    print(A//B)
else:
    # 割り切れない場合は、一回分多く攻撃が必要
    print(A//B + 1)

B – Find snuke

問題

考察

B.1 考えた内容の概要

この問題では、2次元のマス目上に配置された文字列を探索して、特定のパターンが存在する位置を見つけることが求められています。

そのパターンとは、”snuke”という文字列が、縦、横、斜めのいずれかの8方向に連続して並んでいる形です。それを探し出し、そのマスの位置を出力することが目的となります。

B.2 考えたことの内容

上記から、この問題は、ある起点から8つの方向(上下左右と4つの斜め方向)に対して、文字列を探索する問題と捉えることができます。

探索は、起点から指定された方向へ一歩ずつ進んでいきます。
起点からの探索方向と距離を管理するために、移動方向(d)と移動距離(k)を表現するための2つの変数を使用します。

B.3 実装した内容

まず、全マスを順に調べ、そのマスの文字が探索対象の文字列の最初の文字(この場合は’s’)であるかどうかを確認します。
その後、そのマスから8つの方向に対して探索を行い(direction)、それぞれの方向に対して探索対象の文字列と一致する文字列が存在するかどうかを確認します。それが見つかればその位置を出力します。

コード

# HとWはグリッドの高さと幅
H, W = map(int,input().split())

# boxは各セルに含まれる文字を格納するリスト
box = []
for i in range(H):
    # 入力された行を読み込む
    letter = input()
    box.append(letter)

# directionは8つの可能な方向を示す(右、左、上、下、右上、右下、左上、左下)
direction = [[0,1],[0,-1],[1,0],[-1,0],[1,1],[1,-1],[-1,1],[-1,-1]]

# wordは見つけるべき単語
word = "snuke"

# グリッドをループする
for i in range(H):
    for j in range(W):
        # 現在のセルが's'で始まる場合、8つの方向に単語を検索開始
        if box[i][j] == "s":
            for d in range(8):
                # move_i, move_jは現在調査中のセルの位置
                move_i = i
                move_j = j
                # ansは単語の文字が見つかったセルのリスト
                ans = []
                for k in range(5):
                    # 位置が範囲内であり、かつ該当のセルに検索中の文字がある場合
                    if  0 <= move_i < H and 0 <= move_j < W and box[move_i][move_j] == word[k]:
                        # そのセルの位置をリストに追加
                        ans.append([move_i,move_j])
                        # 次のセルへ移動
                        move_i += direction[d][0]
                        move_j += direction[d][1]
                    else:
                        # 条件が満たされない場合はループを中断
                        break

                # 'snuke'のすべての文字が見つかった場合
                if len(ans) == 5:
                    # 各文字の位置を表示
                    for l in range(5):
                        print(ans[l][0]+1, ans[l][1]+1)

C – Almost Equal

問題

考察

C.1 考えた内容の概要

この問題は、一連の文字列が特定の条件を満たす順序で並べられるかどうかを判断するものです。
各文字列が1つ前の文字列とちょうど1文字だけ異なるように並べられるかどうかを判断します。

C.2 考えたことの内容

最初に思いつくのは、すべての可能な文字列の順序を試し、その中に条件を満たすものがあるかどうかを確認するという方法です。

Nが小さい(N<8)ため、全探索が可能です。全ての順列は、8の階乗(40320)であり、これは計算上実行可能です。

そして、条件をチェックする方法としては、「ハミング距離」を利用します。
ハミング距離とは、同じ長さの二つの文字列が異なる位置の数を数えることです。これにより、二つの文字列がちょうど一文字だけ異なるかどうかを判断できます。

C.3 実装した内容

Pythonのitertoolsモジュールのpermutations関数を使って、全ての文字列の順列を生成します。

各順列について条件を満たすかどうかを確認します。

コード

import itertools

# 入力
N, M = map(int,input().split())
letter_list = []
for i in range(N):
    letter = input()
    letter_list.append(letter)

#ハミング距離を求める
def check(l1,l2):
    cnt = 0
    for i in range(M):#文字列を前から見て言って
        if l1[i] != l2[i]: #i番目の文字が異なるなら
            cnt +=1
    if cnt == 1: # 一文字だけ異なるなら
        return True
    else:
        return False

# 全並び替えを探索
for p in itertools.permutations(range(N)):
    is_flg = True
    for i in range(N-1):
        if check(letter_list[p[i]],letter_list[p[i+1]]) == False: #条件を満たさないなら
            is_flg = False
            break
    if is_flg: # 条件を満たすなら
        print("Yes")
        exit()

print("No")

D – Impartial Gift

問題

考察

D.1 考えた内容の概要

この問題では、高橋君が青木君とすぬけ君にそれぞれ贈り物を送る際に、贈り物の価値の差がD以下になるように選ぶことが求められています。それぞれの贈り物の選択肢がある中で、価値の和が最大となるような組み合わせを見つけることが目標です。

D.2 考えたことの内容

全ての組み合わせを調べ上げる方法は計算量が大きすぎるため、工夫が必要です。
そこで、事前に贈り物の価値のリストをソートしておきます。その上で、青木君への贈り物の候補を全て調べることにし、それぞれの候補について二分探索を用いて、すぬけ君への贈り物との価値の差がD以下となる最大のものを探します。
これにより、全ての組み合わせを調べ上げる必要がなくなり、計算量を削減することが可能となりますO(N log (M))。

D.3 実装した内容

Pythonのbisectモジュールを使って、各候補について二分探索を行います。
その際、価値の差がD以下となるすぬけ君への贈り物の最大のものを探すため、条件を満たすものを見つけたらそのインデックスを記録します。全ての青木君への贈り物の候補について探索が終わったら、最大の価値の和を出力します。

コード

import bisect

# 入力
N, M, D = map(int, input().split())
A = list(map(int, input().split()))
B = list(map(int, input().split()))

# ソート
A.sort()
B.sort()

max_sum = -1
for i in range(N):
    # 二分探索
    j = bisect.bisect_right(B, A[i]+D)
    # Bの候補がない場合
    if j == 0:
        if abs(A[i] - B[j]) <=D:
            max_sum = max(max_sum, A[i] + B[j+1])
    # Bの候補が末尾の場合
    elif j >= M-1:
        if abs(A[i] - B[j-1]) <=D:
            max_sum = max(max_sum, A[i] + B[j-1])
    else:
        # 差がD以下になるBの候補を見つけて価値の和を更新
        if abs(A[i] - B[j-1]) <=D:
            max_sum = max(max_sum, A[i] + B[j-1])
        if abs(A[i] - B[j]) <=D:
            max_sum = max(max_sum, A[i] + B[j])

print(max_sum)

E – Isolation

問題

考察

時間内にできなかったよー。ぴえん

E.1 考えた内容の概要

この問題では、グラフ上で頂点間の辺の追加と削除の操作を行い、各操作後に接続されていない頂点の数を求めるという問題です。
問題の要件を満たすには、グラフの状態を効率的に管理する方法が求められます。

E.2 考えたことの内容

無向グラフの頂点の接続情報は、各頂点に接続している他の頂点の集合で管理できます。この集合はPythonのsetデータ構造を使用すると効率的に管理できます。setは、辺の追加と削除の操作がそれぞれO(logN)なので、今回の問題でも高速に解くことができます。(知らなかったー)

query1 :
辺の追加操作では、対象となる2つの頂点の集合に互いを追加します。
それぞれの頂点が接続されていなかった場合、接続されていない頂点の数を減らします

query2 :
辺の削除操作では、対象となる頂点が接続している全ての頂点から対象の頂点を削除します。その後、対象の頂点の集合を空にします。
削除操作前に対象の頂点が接続していた場合、接続されていない頂点の数を増やします。

E.3 実装した内容

上記の考察に基づき、Pythonで実装したコードは以下の通りです。このコードでは、各頂点の接続情報をsetで管理し、接続されていない頂点の数を整数で管理します。

コード

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

# 各頂点の接続情報を管理するリスト
node = [set() for i in range(N)]
# 接続されていない頂点の数
cnt = N

for i in range(Q):
    query = list(map(int, input().split()))

    if query[0] == 1:
        u = query[1]-1
        v = query[2]-1

        origin_u = len(node[u])
        origin_v = len(node[v])

        # 頂点uとvを接続
        node[u].add(v)
        node[v].add(u)

        # 頂点uとvがそれぞれ接続されていなかった場合、接続されていない頂点の数を減らす
        if origin_u == 0:
            cnt -= 1
        if origin_v == 0:
            cnt -= 1

        print(cnt)

    elif query[0] == 2:
        u = query[1]-1

        # 頂点uが接続されていなかった場合、何もしない
        if len(node[u]) == 0:
            print(cnt)
        else:
            # 頂点uと接続している全ての頂点から頂点uを削除
            for v in node[u]:
                node[v].discard(u)
                # 頂点vが接続されていなくなった場合、接続されていない頂点の数を増やす
                if len(node[v]) == 0:
                    cnt += 1
            # 頂点uの集合を空にする
            node[u] = set()
            # 頂点uが接続されていた場合、接続されていない頂点の数を増やす
            cnt += 1

            print(cnt)

感想

1000点取っても、3000位くらいなのツラミ

コメント

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