ABC 374, PrimArray

ABC 374

ABC 374 に参加しました。

問題A 問題B 問題C 問題D 問題E 問題F 問題
提出ACACACACAC-
予想 diff101004008001,0001,600
実際 diff11282266941,5042,026
Table 1: Diff 予想

A 問題

文字列が san で終わるなら Yes, そうでなければ No を印字せよ。改行文字に注意して答えます。

main=interact$y.take 4.reverse;y"\nnas"="Yes";y _="No"

B 問題

文字列 s, t が等しいならば 0 を、異なるならば最初に不一致となる位置を答えよ。

cojna さんの提出 を見てみましょう。短過ぎです。

main=interact$show.f.lines
f[s,t]=sum[s%t|s/=t] -- 1
(x:s)%(y:t)|x==y=1+s%t -- 2
_%_=1 -- 3

C 問題

整数列 {Ki}i\{K_i\}_i を 2 つのグループに分けたとき、グループ毎の和の差を最小化せよ。慣れると反射で書ける問題ですね。

solve :: StateT BS.ByteString IO ()
solve = do
  !n <- int'
  !xs <- intsU'

  let !s = U.sum xs
  let candidates = U.generate (bit n) id -- 1
  let eval bits =
        let is = U.findIndices (testBit bits) $ U.generate n id -- 2
            sum1 = U.sum $ U.backpermute xs is -- 3
            sum2 = s - sum1
         in max sum1 sum2

  printBSB . U.minimum $ U.map eval candidates

D 問題

順列と bit mask を全探索せよ。最近ほぼ同じ問題が最近出た覚えが? 順列を固定した上で、 bit mask の適用は DP にするとさらに良しです。

dist :: Int -> Int -> Int -> Int -> Double
dist !x1 !y1 !x2 !y2 = sqrt . intToDouble $ (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)

solveDP :: [(Int, Int, Int, Int)] -> Double
solveDP = inner 0.0 0 0
  where
    inner :: Double -> Int -> Int -> [(Int, Int, Int, Int)] -> Double
    inner !acc !_ !_ [] = acc
    inner !acc !x1 !y1 ((!x2, !y2, !x3, !y3) : rest) =
      let !cand1 = inner (acc + dist x1 y1 x2 y2) x3 y3 rest
          !cand2 = inner (acc + dist x1 y1 x3 y3) x2 y2 rest
       in min cand1 cand2 -- 2

solve :: StateT BS.ByteString IO ()
solve = do
  (!n, !speedM, !speedL) <- ints3'
  !lines <- U.replicateM n ints4'

  let lengthL = U.sum $ U.map (\(!x1, !y1, !x2, !y2) -> dist x1 y1 x2 y2) lines
  let lengthM = minimum . G.map (solveDP . U.toList) $ lexPerms lines -- 1
  printBSB $ lengthM / intToDouble speedM + lengthL / intToDouble speedL -- 3

E 問題

工程のボトルネックを最大化する機械の購入方法を答えよ。反射で答えると、判定問題で 2 分探索ができそうです。

判定問題では ax+by≥wax + by \ge w の下で px+qypx + qy を最大化します。基本的に効率の良い機械を購入すれば良いのですが、実は w mod lcm(a,b)w \bmod \mathrm{lcm}(a, b) 部分の最適解が分かりません。ここで a,b≤100a, b \le 100 より余りの部分は全探索できます。

ヤケクソで探索範囲を広げたら通りました。運です。

F 問題

DP です。 O(M2log⁡M)(M=104)O(M^2 \log M) (M = 10^4) 解法は思いつくつもりなのですが……。考えます。

Note

最近は満足の行く実装が書けるようになり、以前ほど人の提出を読まなくなりました。良いのか悪いのか……。 Common Lisp, Fortran, OCaml あたりも読んでみたいですが、手つかずのままです。

Haskell

Data.Primitive.PrimArray はあまり使わない

Mutable なデータを IORef に入れると (2 重に) box 化されて重くなります。 Library Checker の問題で 200 - 300 ms 程度影響が出る程度には遅く、絶対に避けたいレベルです。

代わりに unboxed な MVector にデータを入れることが多いのですが、 PrimArray の方が速い可能性が出てきました。

検証

雑な検証になりますが、 Library Checker の Splay Tree の問題 (Dynamic Sequence Range Affine Range Sum) で比較しました。 コード差分 。

誤差レベルで遅くなりました (!?) 。リジャッジすれば結果はひっくり返るかも。

なお MVector 版で使用していた unsafeModifyM の実装 を見てみると、 stToPrim により型パラメータ m を ST に確定させ最適化しています。とりあえず使う分には MVector の方が無難かもしれません。

ACL 移植メモ

Disjoint Set Union (Union-Find Tree)

サクっと移植できました。先行きの良いスタートです。

Fenwick Tree (Binary Index Tree)

添字の動きに戸惑いながら移植しました。群の区間和が取れて定数倍が良いです。

Floor sum

何も分からない。離散化すると周期性が出てくる覚えはあります。完全な写経です。

Max flow

可変長配列

可変長配列 (std::vector) に依存しています。 VUM.MVector をラップして可変長配列を実装しました。しかし MVector を MutVar の中に入れるため効率は落ちます。

Break

ループの break のため、ループを再帰関数で表現しました。 break しなくて済む場合でも、なるべく忠実に実装したいと思います。

イテレーションと destructuring

タプルの unboxed vector は SoA (struct of arrays) なので、走査しない配列はスキップした方が速そうな気がしています。速度の比較はしていません。

(VUM.MV_3 _ vecTo _ vecCap) <- mutableVector
neighbors <- VU.zip <$> VU.unsafeFreeze vecTo <*> VU.unsafeFreeze vecCap
VU.forM_ neighbors $ \(!to, !cap) -> do
  -- ~~

テスト

ac-library の単体テストの一部を写経しました (Tests/MaxFlow.hs) 。ランダムテストや PBT の方がカバレッジは良さそうです。

[WIP] Min cost flow

CSR (comperssed sparse row)

CSR の抽象が Haskell だと上手く行きません。一部の要素が可変だったり、イテレーションの効率 (前述) であったり……。 cojna さんと同様に、 min cost flow 専用の CSR を作るのが良いと思いました。汎用の CSR を作るのは難しそうです。

Binary Heap

Binary Heap の実現に std::push_heap と std::pop_heap が使われていました。 std::vector をヒープとして使うための関数で、やや原始的なため戸惑いました。