4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AtCoder 469 振り返り

4
Last updated at Posted at 2026-08-02

AtCoder469振り返りです。
今週は前回に続いて連続して参加しました

結果

AB2完でした。やらかしたー

スクリーンショット 2026-08-02 11.43.52.png

スクリーンショット 2026-08-02 11.51.49.png

ふりかえり

A問題

問題文通りにとくだけです

main :: IO ()
main = do
  [n, k] <- getInts
  print $ n - k + 1

B問題

これも問題文通りに解くだけです。
パターンマッチの便利さに頼って再帰で頑張りました

solve :: String -> Int
solve s
  | s == "x" = 1
  | s == "o" = 0
  | take 2 s == "xx" = go 1 s
  | take 2 s /= "xx" = go 0 s
  | otherwise = error $ "invalid input s: " <> s
  where
    go cnt ['x', 'x'] = cnt + 1
    go cnt [_, _] = cnt
    go cnt ('x' : lst@('x' : 'x' : _)) = go (cnt + 1) lst
    go cnt (_ : st) = go cnt st
    go _ s = error $ "invalid pattern s: " <> s

-- Main
main :: IO ()
main = do
  _ <- readLn @Int
  s <- getLine
  print $ solve s

ちなみに、先頭と末尾にxを追加してzipWith3を使うともっとシンプルに解けます。

-- >>> solve "xxoxxxox"
-- >>> solve "ooooo"
-- >>> solve "x"
-- 2
-- 0
-- 1
solve :: String -> Int
solve s = length $ filter id $ zipWith3 (\c1 c2 c3 -> all (== 'x') [c1, c2, c3]) s' (drop 1 s') (drop 2 s')
  where
    s' = ('x' : s) <> "x"

-- Main
main :: IO ()
main = do
  _ <- readLn @Int
  s <- getLine
  print $ solve s

C問題

問題文に書いてある通りにシミュレーションしたところ、TLEになってしまいました。
1≤N≤8×10^5の制約であること、この解法だと計算量がO(N^2)であることから、最悪6.4 x 10 ^ 11オーダーの計算量になってしまうためTLEになってしまいます。

TLE解法

solve :: String -> Int -> Int
solve s k = if cnt0 > 0 then go (k, cnt0 - 1) sr else k
  where
    (sl, sr) = splitAt k s
    cnt0 = length [() | c <- sl, c == 'o']
    go (score, _) [] = score
    go (score, rem) ('o' : st) = go (score + 1, rem) st
    go (score, rem) ('x' : st)
      | rem <= 0 = score + 1
      | rem > 0 = go (score + 1, rem - 1) st

-- Main
main :: IO ()
main = do
  n <- readLn @Int
  s <- getLine
  mapM_ (print . solve s) [1 .. n]

では制限時間内で解くためにはどうすればよいのでしょうか

解説にはこのように書かれています

S に含まれる x の個数が k 個未満ならばすべての袋を受け取ることができ、k 個以上ならば k 番目の「はずれ」の袋まで受け取ることができます。

思ったより解法の考え方は簡単そうです。より納得できるよう、具体例で考えてみましょう。

oxoxok=2の場合を考えてみます。
問題文通りにシミュレーションすると、

最初に持っている袋: ox
oを持っていることから次の袋を受け取れます。oを捨てて次の袋を取ります。

1つ目の操作後に持っている袋: xo
oを持っていることから次の袋を受け取れます。oを捨てて次の袋を取ります。

2つ目の操作後に持っている袋: xx
全ての袋がxになっているため、ここで操作を中断します。

このとき、取った袋は4つです。そして、k=2個目の袋に該当します。
解説通りになってますね。

この具体例をもとに考察すると、次のことが言えそうです。

  • 操作の途中にoが出てくる -> oを捨てて次の袋を取る。つまり手持ちの袋に増減はなく次の操作に進む。そのため途中に出てくるoは気にしなくてよい
  • 操作の途中にxが出てくる -> 持っているoを捨ててxを加える。つまり持っている袋にxを加える。これがk個になればそれ以上操作ができなくなる

=> xk個出てくるまで袋を取ることができる。

これは以下のように言い換えることができます。

xがk個出てきた時のindex(k個出るまでに取った袋の数)が答え

ここまでくれば実装に起こせそうです。実装は以下のようになります。

最初にzip [1..]を使ってindexを付与しておき、xでfilterしつつ出現回数をkey, 出現回数を満たしたindexをvalueとしてIntMapを作りました。

-- >>> solve 5 "oxoxo"
-- >>> solve 3 "ooo"
-- >>> solve 1 "x"
-- >>> solve 5 "xxxoo"
-- >>> solve 5 "oooxo"
-- [2,4,5,5,5]
-- [3,3,3]
-- [1]
-- [1,2,3,5,5]
-- [4,5,5,5,5]
solve :: Int -> String -> [Int]
solve n s = map f [1 .. n]
  where
    im = IM.fromList $ tail $ scanl' (\(cnt, _) (idx, _) -> (cnt + 1, idx)) (0, 0) $ filter (\(_, c) -> c == 'x') (zip [1 ..] s)
    f k = fromMaybe n (IM.lookup k im)

-- Main
main :: IO ()
main = do
  n <- readLn @Int
  s <- getLine
  mapM_ print (solve n s)

解法の考え方そのものがわかればシンプルですが、そこに辿り着くのが難しい問題だったように思います。

(補足)array関数について

最初この問題をarray関数を使って解こうとしたのですが、いくつか罠があったので補足です。

arrayを使って以下のように実装していたのですが、この場合エラーが発生してしまいます。
!?を使ってMaybeを返すようにしてもダメです。

Array版

-- >>> solve 5 "oxoxo"
-- >>> solve 3 "ooo"
-- >>> solve 1 "x"
-- >>> solve 5 "xxxoo"
-- >>> solve 5 "oooxo"
-- (Array.!): undefined array element
-- (Array.!): undefined array element
-- [1]
-- (Array.!): undefined array element
-- (Array.!): undefined array element
solve :: Int -> String -> [Int]
solve n s = map f [1 .. n]
  where
    arr = array @Array (1, n) $ tail $ scanl' (\(cnt, _) (idx, _) -> (cnt + 1, idx)) (0, 0) $ filter (\(_, c) -> c == 'x') (zip [1 ..] s)
    f k = fromMaybe n (arr IA.!? k)

UArray版だとこうなります。どうも初期値0が入っていて、indexアクセスに失敗したときはこの値を返すようです。

-- >>> solve 5 "oxoxo"
-- >>> solve 3 "ooo"
-- >>> solve 1 "x"
-- >>> solve 5 "xxxoo"
-- >>> solve 5 "oooxo"
-- [2,4,0,0,0]
-- [0,0,0]
-- [1]
-- [1,2,3,0,0]
-- [4,0,0,0,0]
solve :: Int -> String -> [Int]
solve n s = map f [1 .. n]
  where
    arr = array @UArray (1, n) $ tail $ scanl' (\(cnt, _) (idx, _) -> (cnt + 1, idx)) (0, 0) $ filter (\(_, c) -> c == 'x') (zip [1 ..] s)
    f k = fromMaybe n (arr IA.!? k)

array関数は引数としてbounds (i,i)とindexとvalueのタプルのリスト[(i, e)]を受け取るのですが、このリストに指定していない値については以下のようになってしまうようです。

  • Arrayの場合 -> エラー
  • UArrayの場合 -> eの型に応じた初期値(Intの場合は0)

!?maybeを返すようにしても、この関数は範囲チェックしかしないので初期化されていないindexについてはどうもエラーを発生させてしまう挙動になっているようでした。
https://hackage.haskell.org/package/array-0.5.8.0/docs/src/Data.Array.Base.html#%21%3F

arrayでArrayを作っても、indexアクセスが部分関数になってしまうと扱いづらいので、array関数を使うのは避けた方がよさそうです。

自分はこの対処としてIntMapを使ったのですが、accumArrayを使って回避する方法がありそうでした(とーらすさんありがとうございます!)

これは以下のようにaccumArrayflip constを使うことで、初期化した値をそのまま置き換えていく実装になります。

let = accumArray @UArray (flip const) n (1, n) [(i, e) | 色々操作]

これを使うことで、初期値を実装者側で決めつつindexとvalueのタプルリストから安全にArrayを作ることができます。

今回indexアクセスに失敗したらnを返したいので、初期値はnとします。
(xの個数がkとなることがありえない場合は全て取得する、つまりnを返したい)

この場合は実装はこうなります。

-- >>> solve 5 "oxoxo"
-- >>> solve 3 "ooo"
-- >>> solve 1 "x"
-- >>> solve 5 "xxxoo"
-- >>> solve 5 "oooxo"
-- [2,4,5,5,5]
-- [3,3,3]
-- [1]
-- [1,2,3,5,5]
-- [4,5,5,5,5]
solve :: Int -> String -> [Int]
solve n s = map (arr !) [1 .. n]
  where
    arr = accumArray @UArray (flip const) n (1, n) $ tail $ scanl' (\(cnt, _) (idx, _) -> (cnt + 1, idx)) (0, 0) $ filter (\(_, c) -> c == 'x') (zip [1 ..] s)

-- Main
main :: IO ()
main = do
  n <- readLn @Int
  s <- getLine
  mapM_ print (solve n s)

無事ACしました。
https://atcoder.jp/contests/abc469/submissions/78066056

全体を振り返って

今回のC問題は解法に気づくことができず最後まで解くことができませんでした。
解法が簡単なので、気づけば解けたのにー!という気持ちになってしまいますが、原因を「気づけたら」に求めてしまうと再現性がなく同じ失敗を繰り返してしまいそうです。

正直こういう問題を確実に解けるためにどうすればいいかわからないですが、時間をかけて丁寧に考察することで思考の精度を高めて筋の良い解法を探していくしかなさそうな気がしています。

なかなか伸びませんが、引き続き泥臭くやるしかなさそうです。
時間がなく睡眠時間を削りながらになってしまっていますが、引き続き頑張りますー!

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?