Atcoder - ABC359 A - D まで復習解説
ABC359 A - D を復習してまとめてみました。初学者の方の参考になれば幸いです。
A - Count Takahashi
問題文
文字列が個与えられます。 N 番目 ( i ) に与えられる文字列 1 \le i \le N S_{i}
はTakahashi
かAoki
のどちらかと等しいです。が S_{i} Takahashi
と等しい
i
がいくつあるか求めてください。
どこにあるかは関係なくいくつあるか求めるだけなので Python だと Counter
を用いると楽です。
Counter
の返り値は Dict like なオブジェクトなので get
メソッドで値を取り出せますが Takahashi
が1個もない場合は None
が返ってくるので get("Takahashi", 0)
としてデフォルト値を
入力例 2 がちょうど
from collections import Counter
N = int(input())
S = [input() for _ in range(N)]
counts = Counter(S)
ans = counts.get("Takahashi", 0)
print(ans)
B - Couples
2通りの解き方を紹介しておきます。
1. A[i] と A[i + 2] が等しいか調べる
公式解法の考え方です。「色
そこで後者を調べるために前から for を回して「
N = int(input())
A = list(map(int, input().split()))
ans = 0
for i in range(2 * N - 2):
if A[i] == A[i + 2]:
ans += 1
print(ans)
i 色の服を着ている二人 に注目する方法
2. コンテスト中は 「「色
正直公式解法のほうが楽ですがこういう書き方が役に立つこともあるかなと思うので紹介しておきます。
と定義し
N = int(input())
A = list(map(int, input().split()))
G = [[] for _ in range(N + 1)]
for i, a in enumerate(A):
G[a].append(i)
ans = 0
for g in G[1:]:
if g[0] + 2 == g[1]:
ans += 1
print(ans)
C - Tile Distance 2
以前 Tile Distance という問題を解けなかったので少し苦手意識を持ってしまいましたが、怖がらずゆっくり読み解くことで AC することができました。
まず、出力例1の図をじっくり見てみました。
思いついたことをまとめると以下のようになりました。
-
方向への移動とx 方向への移動は非対称であるy -
方向は2マス進んでコスト1かかるx -
方向は1マス進むごとにコスト1かかるy - つまりそれぞれ独立に考えるということができない
-
-
方向へ移動した後にy 方向へ1回ノーコストで移動できるx - 画像だと1つ目と4つ目の赤丸の後にノーコストで左に移動している
- 1つ目と2つ目の赤丸の後に左に移動してても問題ない、できる限りさっさと左に移動したほうがよさそう
以上のことから
- 先に
方向へ移動するy - ついでに
方向へ1回ノーコストで移動するx - 1, 2 をゴールと
が一致するまで繰り返すy - 最後に足りない分だけ
方向へコストをかけて移動するx
という方針を立て実装しました。
スタートとゴールの位置関係は
- スタートから見てゴールは右上にある
- スタートから見てゴールは右下にある
- スタートから見てゴールは左上にある
- スタートから見てゴールは左下にある
の4パターンがありすべてを考えると複雑になってしまうので、スタートとゴールを入れ替えてもかかるコストは変わらなさそうということから
- スタートから見てゴールは右上にある
- スタートから見てゴールは右下にある
- スタートから見てゴールは左上にある → スタートとゴールを入れ替えると 2 になる
- スタートから見てゴールは左下にある → スタートとゴールを入れ替えると 1 になる
とすることから2パターンへ減らし、さらにスタートから見たゴールの
- スタートから見てゴールは右上にある
- スタートから見てゴールは右下にある → ゴールの
座標を上下反転して 1 になるy
とし、1パターンのみの実装になります。
実装のパターンを減らしバグや考えることを減らすことはよく出てくるテクニックですね。
(とはいっても私はコンテスト中は2パターンまでにしか減らせませんでしたが)
1パターンのみ考えた実装例は以下のようになります。
sx, sy = map(int, input().split())
gx, gy = map(int, input().split())
if sx > gx:
# 常にスタートが左側になるようにする
sx, sy, gx, gy = gx, gy, sx, sy
if sy > gy:
# 常にスタートが下側になるようにする
gy = (gy - sy) * (-1) + sy
# 今いる 2x1 のタイルの左側にいるならノーコストで右側に移動する
if sy % 2 == sx % 2:
# 左側にいる
sx += 1
cost = 0
# 先に上に移動する。移動するたびノーコストで1マス右側に移動できる
dy = gy - sy
cost += dy
sx = min(sx + dy, gx)
# 右に移動する。コスト1で2マス進める
dx = gx - sx
cost += (dx + 1) // 2
print(cost)
D - Avoid K Palindrome
コンテスト中は DP は DP でも区間 DP かなと難しく考えすぎて解くことができませんでした。解説を見た後に Upsolve しました。
前から A
, B
のいずれかの文字をくっつけても同様に 良い文字列 であるかに関与しているのは今くっつけようとしている A
, B
と
したがって私は区間 DP だと勘違いしてしまいましたが
と定義し、前から順に DP をしていけばいいです。
(定義的にも間違ってないと思いますが、便宜上
最大である A
, B
, AA
, AB
, ..., BBBBBBBBA
, BBBBBBBBB
となり多くて
S
が文字列となるので DP
は2次元リストで宣言されることが多いですが今回は defaultdict を用いたほうが実装が楽でした。
from collections import defaultdict
def is_palindrome(s: str) -> bool:
return s == s[::-1]
N, K = map(int, input().split())
S = input()
MOD = 998244353
dp = defaultdict(int)
dp[""] = 1 # 空文字列をスタートとする
for i in range(N):
if S[i] == "?":
s = ["A", "B"] # ? は A, B 両方のパターンを考える
else:
s = [S[i]]
dp2 = defaultdict(int)
for key, value in dp.items():
for si in s:
key2 = key + si
if len(key2) == K and is_palindrome(key2):
# 良い文字列ではないのでスキップ
continue
# K 文字の key2 を K - 1 文字だけにする
key2 = key2[-(K - 1) :]
dp2[key2] += value
dp2[key2] %= MOD
dp = dp2
ans = 0
for value in dp.values():
ans += value
ans %= MOD
print(ans)
Discussion