ABC 356

ABC 356 に参加しました。今回から diff の予想結果を記載します。

Table 1: Diff 予想
問題 A 問題 B 問題 C 問題 D 問題 E 問題 F 問題
予想 100 300 700 1,100 2,000 1,700
実際 18 40 568 886 1506 2152

改めて、序盤の振り返りではコードゴルフをやっています。自己満ですが、億劫になって『やるだけ』と書くよりは良い気がしています。

A 問題

無名関数 (\-> .. ) を通常の関数にすると 1 文字節約できるみたいです。

main=interact$unwords.map show.f.map read.words;f[n,l,r]=[1..l-1]++reverse[l..r]++[r+1..n]
main=interact$unwords.map show.(\[n,l,r]->[1..l-1]++reverse[l..r]++[r+1..n]).map read.words

B 問題

zipWith 祭りで面白かったです。 zipWith の良い点は 2 変数関数が使える点で、演算子をポイントフリースタイルで書けます。

main=interact$f.map read.words
f(n:m:r)=y.and$zipWith(<=)(take m r)$foldl(zipWith(+))[0|_<-[1..m]]$take n.tail$iterate(drop m)r
y True="Yes"
y False="No"

C 問題

読解の難しい問題でした。 Data.Bits を使用しました。

bit 関数と .&. 演算子の使い方
ghci> -- i 番目の bit (のみ) を立てる
ghci> bit 0 :: Int
1
ghci> bit 1 :: Int
2
ghci> bit 2 :: Int
4

ghci> -- bit 積を取る
ghci> 0b11 .&. 0b01 :: Int -- 3 .&. 1
1
ghci> 0b11 .&. 0b10 :: Int -- 3 .&. 2
2
ghci> 0b11 .&. 0b11 :: Int -- 3 .&. 3
3

ghci> -- 立っている bit の数を数える
ghci> popCount (0b11 :: Int)
2
ghci> popCount (0b10 :: Int)
1
ghci> popCount (0b00 :: Int)
0

ビット全探索は [0 .. bit n - 1] (リスト) または U.generate (bit n) id (vector) です。

D 問題

D 問題は桁 DP で解きました。 \(k < N\) ならば各 bit は 0 または 1 を自由に取ることができるため、 \(k = N\) と \(k < N\) で場合分けします。

難し過ぎて、 QuickCheck で愚直解と高速解を比較しました。参考: QuickCheckで競プロ用Haskellコードをデバッグする

-- 0 ~ 7 の N, M に対して愚直回と高速解を比較する
propQC :: QC.Property
propQC =
  QC.forAll (QC.choose (0, maxN)) $ \n -> do
    QC.forAll (QC.choose (0, maxN)) $ \m -> do
      solveNaive n m QC.=== solve' n m
  where
    maxN = bit 3 - 1 :: Int

-- 100 ケースをテストする
runQC :: IO ()
runQC = QC.quickCheck (QC.withMaxSuccess 100 propQC)

今回は REPL からテスト実行していました。コード編集の度に :r (reload) します:

$ stack repl d/Main.hs
Ok, one module loaded.
Loaded GHCi configuration ..

REPL に入ったお                          ___
                                      /      \
ghci> runQC                         /⌒   ⌒   \
+++ OK, passed 100 tests.        /(● ) (● )   ヽ
                                | ⌒(__人__)⌒      |
100 ケース通ったみたいだけど、    \   `⌒ ´     /
これで提出して大丈夫なんだお?       /             \

   / ̄ ̄\
 /   _ノ  \
 |    ( ●)(●)     このサイズなら 100 も試せば十分だろ、
. |     (__人__)    常識的に考えて……
  |     ` ⌒´ノ   
.  |         }
.  ヽ        }
   ヽ     ノ        \
   /    く  \        \
   |     \   \         \
    |    |ヽ、二⌒)、          \

なお quickcheck を使わずとも forM_ で全ケースをチェックすれば良いです。

E 問題

\(\sum\) の一括計算が思いつかず飛ばしました。調和級数と言われても何も思いつきません。 Upsolve します。

F 問題

平方分割を考えていましたが、セグメント木で解けますね。

セグ木を 2 本用意しました。モノイドも 2 種類です。

これで 1 点から左右に伸ばせる区間が分かります。また区間和を取るために 3 本目のセグ木を使いました。

その他気になる解法には区間を set で管理するテクニックがあります。解説も読んでみます。

PAST (アルゴリズム実技検定)

PAST で出題される問題は典型問題が多いようです。実感としては、 upsolve するのも困難な骨太の問題ばかりです。 PAST (典型問題) が解けるなら十二分の実力があると思います。

PAST 18

第 18 回アルゴリズム実技検定を受験し、結果は中級でした。時間はあったのに解ける問題がありませんでした。やはり上級は相当厳しいです。

PAST 19

第 19 回アルゴリズム実技検定を受験し、上級を取得しました。

2024-06-02-past.png
Figure 1: 15 問中 12 問が解ければ上級

試験期間終了まで、これ以上言及できません。

PAST の出題傾向

Misc

Haskell にはコールスタックが無い

Arithmetic overflow が出ました。

 ghci> testBit (0 :: Int) (-1 :: Int)
 *** Exception: arithmetic overflow

厄介なのは、どのコールが例外を出したのか分からない点です。そもそも Haskell にはコールスタックが無いとかなんとか。確認中です。

Call stacks aren't really call stacks — 0xd34df00d.me

次回は Haskell のデバッガや DAP の使い方を調べていこうかと思います。メモ: アルゴリズム面は区間を set で管理するテクニック