Atcoder - ABC344 A - E まで復習解説
ABC344 A - E を復習してまとめてみました。初学者の方の参考になれば幸いです。
A - Spoiler
問題文
英小文字と|
のみからなる文字列が与えられます。 S は S |
をちょうど 2 個含むことが保証されます。
2 つの|
の間にある文字および|
をから削除した文字列を出力してください。 S
文字列 split
method で3つのリストにし |
の間に当たるインデックス join
method で結合すればOKです。 ||abc
みたいに |
の間にデータがないときもワークするのかちょっと不安でしたが入力例にそのようなものがあり AC でしたので安心して提出しました。
S = input()
T = S.split("|")
ans = "".join([T[0], T[2]])
print(ans)
B - Delimiter
Python ではあまり意識しないですがプログラミング言語だと入力が何行これから存在するかなどは入力として与えられていないと実装が煩雑になる場合があります。今回はその
代わりに
A = []
while True:
a = int(input())
A.append(a)
if a == 0:
break
for a in A[::-1]:
print(a)
C - A + B + C
数列
ただし
そこで set
で管理しましょう。各
N = int(input())
A = list(map(int, input().split()))
M = int(input())
B = list(map(int, input().split()))
L = int(input())
C = list(map(int, input().split()))
Q = int(input())
X = list(map(int, input().split()))
S = set([])
for a in A:
for b in B:
for c in C:
S.add(a+b+c)
for x in X:
if x in S:
print("Yes")
else:
print("No")
D - String Bags
愚直にすべてのパターンを考えてしまうと
そこで
とおいて DP で解きましょう。
どのように各袋の文字を選んでもいいから、現在
遷移で少しめんどくさいのが大抵の DP の遷移は1つ前の
for 文が3個になりますが、各袋の中に入っている文字は最大 10 個なので計算量は
(部分文字列の比較が計算コスト重くないかな?と少し不安でしたが Python でも AC しました。)
T = input()
N = int(input())
A = [input().split()[1:] for _ in range(N)]
INF = float("inf")
dp = [[INF] * (len(T) + 1) for _ in range(len(A) + 1)]
dp[0][0] = 0
for i in range(1, len(A) + 1):
for j in range(len(T) + 1):
# 袋の中のどの文字も追加しない場合
dp[i][j] = min(dp[i][j], dp[i - 1][j])
for s in A[i - 1]:
k = len(s)
# s がTの一部と一致し、文字を追加すする場合
if j + k <= len(T) and T[j:j + k] == s:
dp[i][j + k] = min(dp[i][j + k], dp[i - 1][j] + 1)
ans = dp[len(A)][len(T)]
if ans == INF:
ans = -1
print(ans)
E - Insert or Erase
あまりきれいではないのですがせっかくなのでコンテスト中にどう考えたかをまとめておきます。
まず、どういうデータ構造ならよいかを考えました。 Python の場合リストでデータを表現している場合、末尾以外のデータを削除すると計算量が高くなってしまいます。であれば「値 multiset
がいいのかなと思いましたがよくみると値がソートされていません。
次に「毎回本当に値を消さずに「もう消えています」みたいな情報だけもたせる」とすれば消す作業はなくなり計算量が高くならなさそうと思いました。
そこで消さないで情報として残り続けるのであれば
- 各数字をノードとみなし、最初の配列が横に並んでいるイメージなら、新しく追加されるのはあるノード (以降、親ノード) に下方向にノード (以降、子ノード) が連結しているとみなせる
- ある値
のノードの index を管理する辞書を作成しておくとx でアクセスできるO(1) - 子ノードの連結はあるノードと別のノードの間に連結することもある
と順に思いついていきました。
クエリ1で新たにノード
- 親ノード
に対してまだノードが結合していない場合p - 親ノードの子ノード:
→-1 (まだノードがないのをi で表します)-1 - ノード
の親ノード:i →-1 p
- 親ノードの子ノード:
- 親ノード
に対してすでにノードp が結合している場合c - 親ノードの子ノード:
→c p - ノード
の親ノード:i →-1 p - ノード
の親ノード:c →p i
- 親ノードの子ノード:
と、場合分けをして処理をすれば OK です。
クエリ2の処理は簡単で
最後に、最初に横に並んでいた配列のノードを左から右に走査し消えていかなければ配列に追加していきます。ただし子ノードがある限り先に子ノードを走査します。
計算量はクエリ1ごとにノードを1つ増やしその前後の情報を更新し、クエリ2の時はノード1つの情報を更新するだけなので
N: Final = int(input())
A: Final[list[int]] = list(map(int, input().split()))
# 消えたかどうか(消えたら 1 消えてないなら 0)、親ノード、子ノード、ノードの値
nodes = {}
for i, a in enumerate(A):
nodes[i] = [0, -1, -1, a]
# ある値 x のノード番号を保存しておく、ある値 x は同時に1つしか存在しない
value_info = {}
for i, a in enumerate(A):
value_info[a] = i
Q = int(input())
for i in range(N, N + Q):
query = list(map(int, input().split()))
if query[0] == 1:
_, x, y = query
x_idx = value_info[x]
assert x_idx != -1
# 値 x であるノード x_idx の子ノードとして Node i を値 y として追加
# 現時点で子がいるなら間にいれる
_, _, x_child, _ = nodes[x_idx]
new_parent = x_idx
new_child = x_child
if x_child != -1:
nodes[x_child][1] = i
nodes[x_idx][2] = i
nodes[i] = [0, new_parent, new_child, y]
value_info[y] = i
elif query[0] == 2:
_, x = query
# x を削除する
nodes[value_info[x]][0] = 1
# 縦がある限り縦を表示し、なければ横にずれて表示するのを繰り返す。消されたのは表示しない。
ret = []
for i in range(N):
deleted, _, child, value = nodes[i]
if deleted == 0:
ret.append(value)
cur = child
while cur != -1:
deleted, _, child, value = nodes[cur]
if deleted == 0:
ret.append(value)
cur = child
print(*ret)
実は、この様に前後の情報を持つデータ構造は双方向リストと呼ばれます(コンテスト終了後に気づきました)。
コンテスト後にせっかくなので時間をかけてクラスで実装してみました。
from typing import Any, Final, Optional
from dataclasses import dataclass, field
@dataclass
class Node:
value: int
parent: Optional["Node"] = field(default=None)
child: Optional["Node"] = field(default=None)
def get_value(self) -> int:
return self.value
def get_parent(self) -> Optional["Node"]:
if self.parent is None:
raise ValueError("parent is None")
return self.parent
def get_child(self) -> Optional["Node"]:
if self.child is None:
raise ValueError("child is None")
return self.child
def set_value(self, value: int) -> None:
self.value = value
def set_parent(self, parent: "Node") -> None:
self.parent = parent
def set_child(self, child: "Node") -> None:
self.child = child
def connect(self, parent: "Node") -> None:
old_child = parent.child
parent.set_child(self)
self.set_parent(parent)
if old_child is not None:
self.set_child(old_child)
old_child.set_parent(self)
def disconnect(self) -> None:
parent = self.parent
child = self.child
if parent and child:
parent.child = child
child.parent = parent
elif parent:
parent.child = None
elif child:
child.parent = None
else:
raise ValueError("Not connected")
def __str__(self) -> str:
if self.parent and self.child:
return f"Node(value={self.value}, parent=Node({self.parent.value}, ...), child=Node({self.child.value}, ...)"
elif self.parent:
return f"Node(value={self.value}, parent=Node({self.parent.value}, ...), child=None"
else:
return f"Node(value={self.value}, parent=None, child=Node({self.child.value}, ...)"
N: Final = int(input())
A: Final[list[int]] = list(map(int, input().split()))
Q = int(input())
D = {}
head = Node(0)
parent = head
for i, a in enumerate(A):
node = Node(a)
node.connect(parent)
parent = node
D[a] = node
for _ in range(Q):
query = list(map(int, input().split()))
if query[0] == 1:
_, x, y = query
parent = D[x]
node = Node(y)
node.connect(parent)
D[y] = node
elif query[0] == 2:
_, x = query
node = D[x]
node.disconnect()
ret = []
cur = head
while cur.child is not None:
cur = cur.get_child()
ret.append(cur.value)
print(*ret)
Discussion