packages feed

ac-library-hs 1.2.4.0 → 1.2.5.0

raw patch · 6 files changed

+443/−11 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ AtCoder.Extra.Mo: process :: (HasCallStack, PrimMonad m, Unbox a) => Vector Int -> Vector (Int, Int) -> (Int -> m ()) -> (Int -> m ()) -> (Int -> m ()) -> (Int -> m ()) -> (Int -> m a) -> m (Vector a)
+ AtCoder.Extra.Mo: run :: (HasCallStack, PrimMonad m, Unbox a) => Int -> Vector (Int, Int) -> (Int -> m ()) -> (Int -> m ()) -> (Int -> m ()) -> (Int -> m ()) -> (Int -> m a) -> m (Vector a)
+ AtCoder.Extra.Mo: sortIndices :: HasCallStack => Int -> Vector (Int, Int) -> Vector Int
+ AtCoder.Extra.SqrtDecomposition: foldM :: Monad m => Int -> (a -> Int -> m a) -> (a -> Int -> Int -> Int -> m a) -> a -> Int -> Int -> m a
+ AtCoder.Extra.SqrtDecomposition: foldM_ :: Monad m => Int -> (a -> Int -> m a) -> (a -> Int -> Int -> Int -> m a) -> a -> Int -> Int -> m ()
+ AtCoder.Extra.SqrtDecomposition: foldMapM :: (Monad m, Monoid a) => Int -> (Int -> m a) -> (Int -> Int -> Int -> m a) -> Int -> Int -> m a
+ AtCoder.Extra.SqrtDecomposition: foldMapWithM :: Monad m => Int -> (a -> a -> a) -> (Int -> m a) -> (Int -> Int -> Int -> m a) -> Int -> Int -> m a
+ AtCoder.Extra.SqrtDecomposition: forM_ :: Monad m => Int -> (Int -> m ()) -> (Int -> Int -> Int -> m ()) -> Int -> Int -> m ()

Files

CHANGELOG.md view
@@ -1,8 +1,13 @@ # Revision history for acl-hs +## 1.2.5.0 -- April 2025++- Added AtCoder.`Extra.Mo`+- Added AtCoder.`Extra.SqrtDecomposition`+ ## 1.2.4.0 -- April 2025 -- Added `Dsu.mergeMaybe`+- Added `AtCoder.Dsu.mergeMaybe` - Added `AtCoder.Extra.Graph` functions   - `rev`   - `connectedComponents`@@ -16,7 +21,7 @@  ## 1.2.3.0 -- March 2025 -- Added `Extra.SegTree2d` and `Extra.SegTree2d.Dense`.+- Added `AtCoder.Extra.SegTree2d` and `Extra.SegTree2d.Dense`.  ## 1.2.2.1 -- March 2025 @@ -24,16 +29,16 @@  ## 1.2.2.0 -- Feb 2025 -- Added `Extra.KdTree` and `Extra.LazyKdTree`.+- Added `AtCoder.Extra.KdTree` and `Extra.LazyKdTree`. - Added `clear` function to the dynamic segment tree family.-- Fixed `Extra.Hld.new` for a tree with a single vertex.+- Fixed AtCoder.`Extra.Hld.new` for a tree with a single vertex.  ## 1.2.1.0 -- Feb 2025  - Added dynamic segment tree family.-- Added `Extra.Seq.Map`.-- Fixed `Extra.Pool.size`.-- `Handle` is moved from `Extra.Seq` to `Extra.Pool`.+- Added `AtCoder.Extra.Seq.Map`.+- Fixed `AtCoder.Extra.Pool.size`.+- `Handle` is moved from `AtCoder.Extra.Seq` to `AtCoder.Extra.Pool`.  ## 1.2.0.0 -- Feb 2025 @@ -41,9 +46,9 @@ - Tweaked `INLINE` settings for less compile time. - Breaking changes:   - `Matrix.diag` now does not take length parameter.-  - `Extra.Math.primitiveRoot` is renamed to `primitiveRoot32`.+  - `AtCoder.Extra.Math.primitiveRoot` is renamed to `primitiveRoot32`.   - `Internal.Convolution` functions now use `ST` instead of `PrimMonad`.-  - `SegAct` implementation for `Extra.Monoid.RangeAdd` over `Max` and `Min` were fixed.+  - `SegAct` implementation for `AtCoder.Extra.Monoid.RangeAdd` over `Max` and `Min` were fixed.  ## 1.1.1.0 -- Jan 2025 @@ -64,5 +69,5 @@  - First version. - Added ACL-compatible modules.-- Added Extra module of `Math` (binary exponentiation) and `Monoid` (`SegAct` instances).+- Added `AtCoder.Extra.Math` (binary exponentiation) and `AtCoder.Extra.Monoid` (`SegAct` instances). 
ac-library-hs.cabal view
@@ -4,7 +4,7 @@ -- PVP summary:  +-+------- breaking API changes --               | | +----- non-breaking API additions --               | | | +--- code changes with no API change-version:         1.2.4.0+version:         1.2.5.0 synopsis:        Data structures and algorithms description:   Haskell port of [ac-library](https://github.com/atcoder/ac-library), a library for competitive@@ -70,6 +70,7 @@     AtCoder.Extra.KdTree     AtCoder.Extra.LazyKdTree     AtCoder.Extra.Math+    AtCoder.Extra.Mo     AtCoder.Extra.Monoid     AtCoder.Extra.Monoid.Affine1     AtCoder.Extra.Monoid.Mat2x2@@ -87,6 +88,7 @@     AtCoder.Extra.Seq     AtCoder.Extra.Seq.Map     AtCoder.Extra.Seq.Raw+    AtCoder.Extra.SqrtDecomposition     AtCoder.Extra.Tree     AtCoder.Extra.Tree.Hld     AtCoder.Extra.Tree.Lct@@ -149,6 +151,7 @@     Tests.Extra.IntervalMap     Tests.Extra.IntMap     Tests.Extra.IntSet+    Tests.Extra.Ix0     Tests.Extra.KdTree     Tests.Extra.LazyKdTree     Tests.Extra.Math
+ src/AtCoder/Extra/Mo.hs view
@@ -0,0 +1,150 @@+-- | Mo's algorithm for handling \([l, r)\) offline queries in \(O((n + q) \sqrt n f)\) time+-- complecity, where \(n\) is the length of index, \(q\) is the number of queries and \(f\) is the+-- time for processing element addition or deletion. Due to the high time complexity, it is+-- recommended to choose an efficient data structure such as Fenwick Tree for query processing.+--+-- @since 1.2.5.0+module AtCoder.Extra.Mo+  ( run,+    sortIndices,+    process,+  )+where++import Control.Monad (when)+import Control.Monad.Primitive (PrimMonad)+import Data.Bool (bool)+import Data.Foldable (for_)+import Data.Vector.Algorithms.Intro qualified as VAI+import Data.Vector.Generic qualified as VG+import Data.Vector.Generic.Mutable qualified as VGM+import Data.Vector.Unboxed qualified as VU+import Data.Vector.Unboxed.Mutable qualified as VUM+import GHC.Stack (HasCallStack)++-- | \(O((n + q) \sqrt n)\) Runs Mo's algorithm. Internally it's a call of `sortIndices` and+-- `process`.+--+-- @since 1.2.5.0+{-# INLINE run #-}+run ::+  (HasCallStack, PrimMonad m, VU.Unbox a) =>+  -- | Defines index bounds \([0, n)\).+  Int ->+  -- | Query intervals \([l, r)\).+  VU.Vector (Int, Int) ->+  -- | Called on adding left index \(l\).+  (Int -> m ()) ->+  -- | Called on adding left index \(r\).+  (Int -> m ()) ->+  -- | Called on deleting left index \(l\).+  (Int -> m ()) ->+  -- | Called on deleting right index \(r\).+  (Int -> m ()) ->+  -- | Returns result for query index \(i\).+  (Int -> m a) ->+  -- | Result for each query.+  m (VU.Vector a)+run n !lrs !addL !addR !delL !delR !query = do+  let !is = sortIndices n lrs+  process is lrs addL addR delL delR query++-- | \(O(n (\log n))\) Sorts indices of \([l, r)\) queries in an efficient order for processing.+--+-- @since 1.2.5.0+{-# INLINEABLE sortIndices #-}+sortIndices ::+  (HasCallStack) =>+  -- | Defines index bounds \([0, n)\).+  Int ->+  -- | Query intervals \([l, r)\).+  VU.Vector (Int, Int) ->+  -- | Sorted indices to the query intervals.+  VU.Vector Int+sortIndices n !lrs+  | VU.null lrs = VU.empty+  | otherwise = VU.create $ do+      let !q = VU.length lrs+      let !blockLen :: Int = max 1 $ round (sqrt 3 * fromIntegral n / sqrt (fromIntegral (2 * q)) :: Double)+      is <- VUM.generate q id++      -- sort by block index then right:+      VAI.sortBy+        ( \i1 i2 -> do+            let (!l1, !r1) = lrs VG.! i1+                (!l2, !r2) = lrs VG.! i2+                !b1 = l1 `div` blockLen+                !b2 = l2 `div` blockLen+                !res = compare b1 b2 <> bool (compare r2 r1) (compare r1 r2) (even b1)+             in res+        )+        is++      -- The following trick doesn't seem to make it faster though?++      let -- {-# INLINE cost #-}+          cost i1 i2 = do+            (!l1, !r1) <- (lrs VG.!) <$> VGM.read is i1+            (!l2, !r2) <- (lrs VG.!) <$> VGM.read is i2+            pure $ abs (l1 - l2) + abs (r1 - r2)++      when (q > 6) $ do+        for_ [0 .. q - 6] $ \k -> do+          do+            c1 <- cost k (k + 2)+            c2 <- cost (k + 1) (k + 3)+            c3 <- cost k (k + 1)+            c4 <- cost (k + 2) (k + 3)+            when (c1 + c2 < c3 + c4) $ do+              VGM.swap is (k + 1) (k + 2)+          do+            c1 <- cost k (k + 3)+            c2 <- cost (k + 1) (k + 4)+            c3 <- cost k (k + 1)+            c4 <- cost (k + 3) (k + 4)+            when (c1 + c2 < c3 + c4) $ do+              VGM.swap is (k + 1) (k + 3)++      pure is++-- | \(O((n + q) \sqrt n)\) Processes \([l, r)\) interval queries. User would usually use `run`+-- instead.+--+-- @since 1.2.5.0+{-# INLINEABLE process #-}+process ::+  (HasCallStack, PrimMonad m, VU.Unbox a) =>+  -- | Sorted indices to query intervals \([l, r)\).+  VU.Vector Int ->+  -- | Query intervals \([l, r)\).+  VU.Vector (Int, Int) ->+  -- | Called on adding left index \(l\).+  (Int -> m ()) ->+  -- | Called on adding right index \(r\).+  (Int -> m ()) ->+  -- | Called on deleting left index \(l\).+  (Int -> m ()) ->+  -- | Called on deleting right index \(r\).+  (Int -> m ()) ->+  -- | Returns result for query index \(i\).+  (Int -> m a) ->+  -- | Result for each query.+  m (VU.Vector a)+process !is !lrs !addL !addR !delL !delR !query = do+  let !q = VU.length lrs+  !result <- VUM.unsafeNew q++  VU.foldM'_+    ( \(!l0, !r0) i -> do+        let (!l, !r) = lrs VG.! i+        for_ [l0 - 1, l0 - 2 .. l] addL+        for_ [r0, r0 + 1 .. r - 1] addR+        for_ [l0, l0 + 1 .. l - 1] delL+        for_ [r0 - 1, r0 - 2 .. r] delR+        VGM.unsafeWrite result i =<< query i+        pure (l, r)+    )+    (0 :: Int, 0 :: Int)+    is++  VU.unsafeFreeze result
+ src/AtCoder/Extra/SqrtDecomposition.hs view
@@ -0,0 +1,199 @@+-- | Square root decomposition is a technique that divides a sequence of values into around+-- \(\sqrt n\) blocks, aggregating the state information for each block. It allows user to process+-- interval query block by block, typically in \(O(\sqrt n)\) time, where a whole block processing+-- take \(O(1)\) time and partial block processing take \(O(\sqrt n)\) time.+--+-- For simplicity, in this document, we assume that highder order functions applided to an entier+-- block (@readFull@ and @actFull@) work in \(O(1)\) time, and those applied to a part of block work+-- in \(O(\sqrt n)\) time. In total, \(q\) query processing takes \(O(q \sqrt n)\) time. Note that+-- it's a rather large number and often requires performance tuning.+--+-- ==== Lazy propagation+-- Typiaclly, an action to a whole block can be delayed; store the aggregation value for the block,+-- delay the internal sequence update, and restore them when part of the block is accessed. Such+-- lazy propagation should be handled on the user side on partial block access functions+-- (@foldPart@ or @actPart@) are called.+--+-- @since 1.2.5.0+module AtCoder.Extra.SqrtDecomposition+  ( -- | These function signatures try to resemble those for lists.+    forM_,+    foldMapM,+    foldMapWithM,+    foldM,+    foldM_,+  )+where++import AtCoder.Internal.Assert qualified as ACIA+import Control.Monad (when)+import Data.Foldable (for_)+import Data.Vector.Unboxed qualified as VU++-- INLINE all the functions, even if the performance gain is just a little bit.++-- | \(O(\sqrt n)\) Runs user function for each block.+{-# INLINE forM_ #-}+forM_ ::+  (Monad m) =>+  -- | Context: block length.+  Int ->+  -- | Function: @actFull@ function that takes target block index.+  (Int -> m ()) ->+  -- | Function: @actPart@ function that takes target block index, left index and right index.+  (Int -> Int -> Int -> m ()) ->+  -- | Input: \(l\).+  Int ->+  -- | Input: \(r\).+  Int ->+  -- | Unit.+  m ()+forM_ !blockLen !actFull !actPart !l !r = do+  let !_ = ACIA.runtimeAssert (l <= r) "AtCoder.Extra.SqrtDecomposition.forM_: `l <= r` must hold"+  let (!il, !remL) = l `divMod` blockLen+  let (!ir, !remR) = r `divMod` blockLen+  if il == ir+    then do+      when (remR > remL) $ do+        actPart il l r+    else do+      if remL == 0+        then actFull il+        else actPart il l (l - remL + blockLen)+      for_ [il + 1 .. ir - 1] $ \iBlock -> do+        actFull iBlock+      when (remR > 0) $ do+        actPart ir (r - remR) r++-- | \(O(\sqrt n)\) Runs user function for each block and concatanate their monoid output.+--+-- @since 1.2.5.0+{-# INLINE foldMapM #-}+foldMapM ::+  (Monad m, Monoid a) =>+  -- | Context: block length.+  Int ->+  -- | Function: @readFull@ function that takes target block index and returns monoid value of it.+  (Int -> m a) ->+  -- | Function: @readPart@ function that takes target block index, left index and right index, and+  -- returns monoid value for it.+  (Int -> Int -> Int -> m a) ->+  -- | Input: \(l\).+  Int ->+  -- | Input: \(r\).+  Int ->+  -- | Concatenated output.+  m a+foldMapM blockLen = foldMapWithM blockLen (<>)++-- | \(O(\sqrt n)\) Runs user function for each block and concatanates their output with user+-- function.+--+-- @since 1.2.5.0+{-# INLINE foldMapWithM #-}+foldMapWithM ::+  (Monad m) =>+  -- | Context: block length.+  Int ->+  -- | Merges function for output values.+  (a -> a -> a) ->+  -- | Function: @readFull@ function that takes target block index and returns monoid value of it.+  (Int -> m a) ->+  -- | Function: @readPart@ function that takes target block index, left index and right index, and+  -- returns output value of it.+  (Int -> Int -> Int -> m a) ->+  -- | Input: \(l\).+  Int ->+  -- | Input: \(r\).+  Int ->+  -- | Concatenated output.+  m a+foldMapWithM !blockLen !merge !readFull !readPart !l !r = do+  let !_ = ACIA.runtimeAssert (l <= r) "AtCoder.Extra.SqrtDecomposition.foldMapWithM: `l <= r` must hold"+  let (!il, !remL) = l `divMod` blockLen+  let (!ir, !remR) = r `divMod` blockLen+  if il == ir+    then do+      readPart il l r+    else do+      !sx <-+        if remL == 0+          then readFull il+          else readPart il l (l - remL + blockLen)+      !sm <-+        VU.foldM'+          (\ !acc iBlock -> merge acc <$> readFull iBlock)+          sx+          $ VU.generate (ir - 1 - il) (+ (il + 1))+      if remR == 0+        then pure sm+        else do+          rx <- readPart ir (r - remR) r+          pure $! merge sm rx++-- | \(O(\sqrt n)\) Runs user function for each block, performing left folding.+--+-- @since 1.2.5.0+{-# INLINE foldM #-}+foldM ::+  (Monad m) =>+  -- | Context: block length.+  Int ->+  -- | Function: @foldFull@ function that takes target block index and returns monoid value of it.+  (a -> Int -> m a) ->+  -- | Function: @foldPart@ function that takes target block index, left and right local index and returns monoid+  -- value of it.+  (a -> Int -> Int -> Int -> m a) ->+  -- | Initial folding value.+  a ->+  -- | Input: \(l\).+  Int ->+  -- | Input: \(r\).+  Int ->+  -- | Folding result.+  m a+foldM !blockLen !foldFull !foldPart !s0 !l !r = do+  let !_ = ACIA.runtimeAssert (l <= r) "AtCoder.Extra.SqrtDecomposition.foldM: `l <= r` must hold"+  let (!il, !remL) = l `divMod` blockLen+  let (!ir, !remR) = r `divMod` blockLen+  if il == ir+    then do+      foldPart s0 il l r+    else do+      !sx <-+        if remL == 0+          then foldFull s0 il+          else foldPart s0 il l (l - remL + blockLen)+      !sm <-+        VU.foldM'+          foldFull+          sx+          $ VU.generate (ir - 1 - il) (+ (il + 1))+      if remR == 0+        then pure sm+        else foldPart sm ir (r - remR) r++-- | \(O(\sqrt n)\) `foldM` with return value discarded.+--+-- @since 1.2.5.0+{-# INLINE foldM_ #-}+foldM_ ::+  (Monad m) =>+  -- | Context: Block length.+  Int ->+  -- | @readFull@ function that takes target block index and returns monoid value of it.+  (a -> Int -> m a) ->+  -- | @readPart@ function that takes target block index, left and right local index and returns monoid+  -- value of it.+  (a -> Int -> Int -> Int -> m a) ->+  -- | Initial folding value.+  a ->+  -- | Input: \(l\).+  Int ->+  -- | Input: \(r\).+  Int ->+  -- | Unit.+  m ()+foldM_ !blockLen !readFull !readPart !s0 !l !r = do+  _ <- foldM blockLen readFull readPart s0 l r+  pure ()
test/Main.hs view
@@ -16,6 +16,7 @@ import Tests.Extra.IntMap qualified import Tests.Extra.IntSet qualified import Tests.Extra.IntervalMap qualified+import Tests.Extra.Ix0 qualified import Tests.Extra.KdTree qualified import Tests.Extra.LazyKdTree qualified import Tests.Extra.Math qualified@@ -68,6 +69,7 @@             testGroup "Graph" Tests.Extra.Graph.tests,             testGroup "HashMap" Tests.Extra.HashMap.tests,             testGroup "IntervalMap" Tests.Extra.IntervalMap.tests,+            testGroup "Ix0" Tests.Extra.Ix0.tests,             testGroup "IntMap" Tests.Extra.IntMap.tests,             testGroup "IntSet" Tests.Extra.IntSet.tests,             testGroup "KdTree" Tests.Extra.KdTree.tests,
+ test/Tests/Extra/Ix0.hs view
@@ -0,0 +1,73 @@+module Tests.Extra.Ix0 where++import AtCoder.Extra.Ix0+import Test.Tasty+import Test.Tasty.QuickCheck as QC++prop_ix1 :: QC.Gen QC.Property+prop_ix1 = do+  d1 <- QC.chooseInt (1, 10)+  let expected = [0 .. d1 - 1]+  let res = [index0 d1 x1 | x1 <- [0 .. d1 - 1]]+  pure $ res QC.=== expected++prop_ix2 :: QC.Gen QC.Property+prop_ix2 = do+  d2 <- QC.chooseInt (1, 10)+  d1 <- QC.chooseInt (1, 10)+  let expected = [0 .. d2 * d1 - 1]+  let res = [index0 (d2, d1) (x2, x1) | x2 <- [0 .. d2 - 1], x1 <- [0 .. d1 - 1]]+  pure $ res QC.=== expected++prop_ix3 :: QC.Gen QC.Property+prop_ix3 = do+  d3 <- QC.chooseInt (1, 10)+  d2 <- QC.chooseInt (1, 10)+  d1 <- QC.chooseInt (1, 10)+  let expected = [0 .. d3 * d2 * d1 - 1]+  let res = [index0 (d3, d2, d1) (x3, x2, x1) | x3 <- [0 .. d3 - 1], x2 <- [0 .. d2 - 1], x1 <- [0 .. d1 - 1]]+  pure $ res QC.=== expected++prop_ix4 :: QC.Gen QC.Property+prop_ix4 = do+  d4 <- QC.chooseInt (1, 10)+  d3 <- QC.chooseInt (1, 10)+  d2 <- QC.chooseInt (1, 10)+  d1 <- QC.chooseInt (1, 10)+  let expected = [0 .. d4 * d3 * d2 * d1 - 1]+  let res = [index0 (d4, d3, d2, d1) (x4, x3, x2, x1) | x4 <- [0 .. d4 - 1], x3 <- [0 .. d3 - 1], x2 <- [0 .. d2 - 1], x1 <- [0 .. d1 - 1]]+  pure $ res QC.=== expected++prop_ix5 :: QC.Gen QC.Property+prop_ix5 = do+  d5 <- QC.chooseInt (1, 10)+  d4 <- QC.chooseInt (1, 10)+  d3 <- QC.chooseInt (1, 10)+  d2 <- QC.chooseInt (1, 10)+  d1 <- QC.chooseInt (1, 10)+  let expected = [0 .. d5 * d4 * d3 * d2 * d1 - 1]+  let res = [index0 (d5, d4, d3, d2, d1) (x5, x4, x3, x2, x1) | x5 <- [0 .. d5 - 1], x4 <- [0 .. d4 - 1], x3 <- [0 .. d3 - 1], x2 <- [0 .. d2 - 1], x1 <- [0 .. d1 - 1]]+  pure $ res QC.=== expected++prop_ix6 :: QC.Gen QC.Property+prop_ix6 = do+  d6 <- QC.chooseInt (1, 10)+  d5 <- QC.chooseInt (1, 10)+  d4 <- QC.chooseInt (1, 10)+  d3 <- QC.chooseInt (1, 10)+  d2 <- QC.chooseInt (1, 10)+  d1 <- QC.chooseInt (1, 10)+  let expected = [0 .. d6 * d5 * d4 * d3 * d2 * d1 - 1]+  let res = [index0 (d6, d5, d4, d3, d2, d1) (x6, x5, x4, x3, x2, x1) | x6 <- [0 .. d6 - 1], x5 <- [0 .. d5 - 1], x4 <- [0 .. d4 - 1], x3 <- [0 .. d3 - 1], x2 <- [0 .. d2 - 1], x1 <- [0 .. d1 - 1]]+  pure $ res QC.=== expected++-- indices should be successive+tests :: [TestTree]+tests =+  [ QC.testProperty "Ix0 1" prop_ix1,+    QC.testProperty "Ix0 2" prop_ix2,+    QC.testProperty "Ix0 3" prop_ix3,+    QC.testProperty "Ix0 4" prop_ix4,+    QC.testProperty "Ix0 5" prop_ix5,+    QC.testProperty "Ix0 6" prop_ix6+  ]