ac-library-hs-1.2.0.0: src/AtCoder/Extra/WaveletMatrix/Raw.hs
{-# LANGUAGE RecordWildCards #-}
-- original implementation:
-- <https://miti-7.hatenablog.com/entry/2018/04/28/152259>
-- NOTE: We could integrate cumulative sum / fenwick tree / segment tree.
-- NOTE: `topK` and `intersects` are not implemented as they are slow.
-- | A static Wavelet Matrix without automatic index comperssion. Consider using
-- @AtCoder.Extra.WaveletMatrix@ instead.
--
-- @since 1.1.0.0
module AtCoder.Extra.WaveletMatrix.Raw
( -- * RawWaveletMatrix
RawWaveletMatrix (..),
-- * Constructors
build,
-- * Access (indexing)
access,
-- * rank
rankLT,
rank,
rankBetween,
-- * Select
select,
selectKth,
selectIn,
selectKthIn,
-- * Quantile (value-ordered access)
-- ** Safe (total)
kthSmallestIn,
ikthSmallestIn,
kthLargestIn,
ikthLargestIn,
-- ** Unsafe
unsafeKthSmallestIn,
unsafeIKthSmallestIn,
unsafeKthLargestIn,
unsafeIKthLargestIn,
-- * Lookup
lookupLE,
lookupLT,
lookupGE,
lookupGT,
-- * Conversions
assocsIn,
assocsWith,
descAssocsIn,
descAssocsInWith,
)
where
import AtCoder.Extra.WaveletMatrix.BitVector qualified as BV
import AtCoder.Internal.Assert qualified as ACIA
import AtCoder.Internal.Bit qualified as ACIB
import Control.Monad.ST (runST)
import Data.Bit (Bit (..))
import Data.Bits (bit, countTrailingZeros, setBit, testBit, (.|.))
import Data.Maybe
import Data.Vector qualified as V
import Data.Vector.Algorithms.Radix qualified as VAR
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)
-- | A static Wavelet Matrix without automatic index comperssion.
--
-- @since 1.1.0.0
data RawWaveletMatrix = RawWaveletMatrix
{ -- | \(\lceil \log_2 N \rceil\).
--
-- @since 1.1.0.0
heightRwm :: {-# UNPACK #-} !Int,
-- | The length of the original array.
--
-- @since 1.1.0.0
lengthRwm :: {-# UNPACK #-} !Int,
-- | The bit matrix. Each row represents (heightRwm - 1 - iRow) bit's on/off.
--
-- @since 1.1.0.0
bitsRwm :: !(V.Vector BV.BitVector),
-- | The number of zeros bits in each row in the bit matrix.
--
-- @since 1.1.0.0
nZerosRwm :: !(VU.Vector Int)
}
deriving (Eq, Show)
-- | \(O(n \log n)\) Creates a `RawWaveletMatrix` from a vector \(a\).
--
-- @since 1.1.0.0
{-# INLINEABLE build #-}
build ::
(HasCallStack) =>
-- | The number of different values in the compressed vector.
Int ->
-- | A compressed vector
VU.Vector Int ->
-- | A wavelet matrix
RawWaveletMatrix
build nx xs
| nx < 0 = error "AtCoder.Extra.WaveletMatrix.Raw.build: given negative `n`"
| otherwise = runST $ do
-- TODO: less mutable variables
orgBits <- VUM.replicate (lengthRwm * heightRwm) $ Bit False
orgCsum <- VUM.replicate (lenCSum * heightRwm) (0 :: Int)
nZeros <- VUM.unsafeNew heightRwm
-- views by row over the contiguous memory:
let !bits = V.unfoldrExactN heightRwm (VUM.splitAt lengthRwm) orgBits
let !csums = V.unfoldrExactN heightRwm (VUM.splitAt lenCSum) orgCsum
-- the vector will be sorted by bits.
vec <- VU.thaw xs
V.izipWithM_
( \iRow bitVec csum -> do
let !iBit = heightRwm - 1 - iRow
vec' <- VU.unsafeFreeze vec
VU.iforM_ vec' $ \i x -> do
VGM.unsafeWrite bitVec i . Bit $ testBit x iBit
-- csum.
VGM.unsafeWrite csum 0 (0 :: Int)
bitVec' <- VU.unsafeFreeze bitVec
-- get popCount by word. TODO: use `castToWords` for most elements
nOnes <- BV.csumInPlace csum bitVec'
VGM.unsafeWrite nZeros iRow (lengthRwm - nOnes)
-- preform a stable sort by the bit:
VAR.sortBy 2 2 (\_ x -> fromEnum (testBit x iBit)) vec
)
bits
csums
nZerosRwm <- VU.unsafeFreeze nZeros
bits' <- V.unfoldrExactN heightRwm (VU.splitAt lengthRwm) <$> VU.unsafeFreeze orgBits
csums' <- V.unfoldrExactN heightRwm (VU.splitAt lenCSum) <$> VU.unsafeFreeze orgCsum
let !bitsRwm = V.zipWith BV.BitVector bits' csums'
pure $ RawWaveletMatrix {..}
where
!lengthRwm = VG.length xs
!lenCSum = (lengthRwm + BV.wordSize - 1) `div` BV.wordSize + 1 -- +1 for the zero
!heightRwm = countTrailingZeros $ ACIB.bitCeil nx
-- | \(O(\log |S|)\) Returns \(a[k]\) or `Nothing` if the index is out of the bounds. Try to use the
-- original array if you can.
--
-- @since 1.1.0.0
{-# INLINEABLE access #-}
access :: RawWaveletMatrix -> Int -> Maybe Int
access RawWaveletMatrix {..} i0
| ACIA.testIndex i0 lengthRwm =
let (!_, !res) =
V.ifoldl'
( \(!i, !acc) !iRow !bits ->
let Bit !goRight = VG.unsafeIndex (BV.bitsBv bits) i
!i'
| goRight = BV.rank1 bits i + VG.unsafeIndex nZerosRwm iRow
| otherwise = BV.rank0 bits i
!acc'
| goRight = setBit acc (heightRwm - 1 - iRow)
| otherwise = acc
in (i', acc')
)
(i0, 0)
bitsRwm
in Just res
| otherwise = Nothing
-- | \(O(\log |A|)\) Goes down the wavelet matrix for collecting the kth smallest value.
--
-- @since 1.1.0.0
{-# INLINEABLE goDown #-}
goDown :: RawWaveletMatrix -> Int -> Int -> Int -> (Int, Int, Int, Int)
goDown RawWaveletMatrix {..} l_ r_ k_ = V.ifoldl' step (0 :: Int, l_, r_, k_) bitsRwm
where
-- It's binary search over the value range. In each row, we'll focus on either 0 bit values or
-- 1 bit values in [l, r) and update the range to [l', r').
step (!acc, !l, !r, !k) !iRow !bits
-- `r0 - l0`, the number of zeros in [l, r), is bigger than or equal to k:
-- Go left.
| k < r0 - l0 = (acc, l0, r0, k)
-- Go right.
| otherwise =
let !acc' = acc .|. bit (heightRwm - 1 - iRow)
!nZeros = VG.unsafeIndex nZerosRwm iRow
-- every zero bits come to the left after the move.
!l' = l + nZeros - l0 -- add the number of zeros in [0, l)
!r' = r + nZeros - r0 -- add the number of zeros in [0, r)
!k' = k - (r0 - l0) -- `r0 - l0` zeros go left
in (acc', l', r', k')
where
!l0 = BV.rank0 bits l
!r0 = BV.rank0 bits r
-- | \(O(\log |A|)\) Goes up the wavelet matrix for collecting the value \(x\).
--
-- @since 1.1.0.0
{-# INLINEABLE goUp #-}
goUp :: RawWaveletMatrix -> Int -> Int -> Maybe Int
goUp RawWaveletMatrix {..} i0 x =
V.ifoldM'
( \ !i !iBit !bits ->
if testBit x iBit
then BV.select1 bits $ i - nZerosRwm VG.! (heightRwm - 1 - iBit)
else BV.select0 bits i
)
i0
(V.reverse bitsRwm)
-- | \(O(\log |S|)\) Returns the number of \(y\) in \([l, r) \times [0, y_0)\).
--
-- @since 1.1.0.0
{-# INLINEABLE rankLT #-}
rankLT :: RawWaveletMatrix -> Int -> Int -> Int -> Int
rankLT RawWaveletMatrix {..} l_ r_ xr
-- REMARK: This is required. The function below cannot handle the case N = 2^i and xr = N.
| xr >= bit heightRwm = r'_ - l'_
| xr <= 0 = 0
| r'_ <= l'_ = 0
| otherwise =
let (!res, !_, !_) = V.ifoldl' step (0, l'_, r'_) bitsRwm
in res
where
-- clamp
l'_ = max 0 l_
r'_ = min lengthRwm r_
-- [l, r)
step (!acc, !l, !r) !iRow !bits =
let !b = testBit xr (heightRwm - 1 - iRow)
!l0 = BV.rank0 bits l
!r0 = BV.rank0 bits r
in if b
then (acc + r0 - l0, l - l0 + VG.unsafeIndex nZerosRwm iRow, r - r0 + VG.unsafeIndex nZerosRwm iRow)
else (acc, l0, r0)
-- | \(O(\log |S|)\) Returns the number of \(y\) in \([l, r)\).
--
-- @since 1.1.0.0
{-# INLINEABLE rank #-}
rank ::
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(y\)
Int ->
-- | The number of \(y\) in \([l, r)\).
Int
rank wm l r x = rankBetween wm l r x (x + 1)
-- | \(O(\log |S|)\) Returns the number of \(y\) in \([l, r) \times [y_1, y_2)\).
--
-- @since 1.1.0.0
{-# INLINEABLE rankBetween #-}
rankBetween ::
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(y_1\)
Int ->
-- | \(y_2\)
Int ->
-- | The number of \(y\) in \([l, r) \times [y_1, y_2)\).
Int
rankBetween wm l r lx rx = rankLT wm l r rx - rankLT wm l r lx
-- | \(O(\log |S|)\) Returns the index of the first \(y\) in the sequence, or `Nothing` if \(y\) is
-- not found.
--
-- @since 1.1.0.0
{-# INLINEABLE select #-}
select :: RawWaveletMatrix -> Int -> Maybe Int
select wm = selectKth wm 0
-- | \(O(\log |S|)\) Returns the index of the \(k\)-th occurrence (0-based) of \(y\), or `Nothing`
-- if no such occurrence exists.
--
-- @since 1.1.0.0
{-# INLINEABLE selectKth #-}
selectKth ::
RawWaveletMatrix ->
-- | \(k\)
Int ->
-- | \(y\)
Int ->
-- | The index of \(k\)-th \(y\)
Maybe Int
selectKth wm = selectKthIn wm 0 (lengthRwm wm)
-- | \(O(\log |S|)\) Given an interval \([l, r)\), it returns the index of the first occurrence
-- (0-based) of \(y\) in the sequence, or `Nothing` if no such occurrence exists.
--
-- @since 1.1.0.0
{-# INLINEABLE selectIn #-}
selectIn ::
-- | A wavelet matrix
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(k\)
Int ->
-- | The index of the first \(y\) in \([l, r)\).
Maybe Int
selectIn wm = selectKthIn wm 0
-- | \(O(\log |S|)\) Given an interval \([l, r)\), it returns the index of the \(k\)-th occurrence
-- (0-based) of \(y\) in the sequence, or `Nothing` if no such occurrence exists.
--
-- @since 1.1.0.0
{-# INLINEABLE selectKthIn #-}
selectKthIn ::
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(k\)
Int ->
-- | \(y\)
Int ->
-- | The index of the \(k\)-th \(y\) in \([l, r)\).
Maybe Int
selectKthIn wm@RawWaveletMatrix {..} l_ r_ k x
| not (0 <= x && x < lengthRwm && 0 <= k && k < lengthRwm) = Nothing
| l'_ < r'_ = inner
| otherwise = Nothing
where
-- clamp
l'_ = max 0 l_
r'_ = min lengthRwm r_
inner :: Maybe Int
inner
| rEnd <= lEnd + k = Nothing
-- go up
| otherwise = goUp wm (lEnd + k) x
where
-- TODO: replace with goDown
-- Go down. Gets the [l, r) range of @x@ in the last array.
(!lEnd, !rEnd) =
V.ifoldl'
( \(!l, !r) !iRow !bits ->
let !l0 = BV.rank0 bits l
!r0 = BV.rank0 bits r
in if testBit x (heightRwm - 1 - iRow)
then (l + nZerosRwm VG.! iRow - l0, r + nZerosRwm VG.! iRow - r0)
else (l0, r0)
)
(l'_, r'_)
bitsRwm
-- | \(O(\log |S|)\) Given an interval \([l, r)\), it returns the index of the \(k\)-th (0-based)
-- largest value. Note that duplicated values are counted as distinct occurrences.
--
-- @since 1.1.0.0
{-# INLINEABLE kthLargestIn #-}
kthLargestIn ::
-- | A wavelet matrix
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(k\)
Int ->
-- | \(k\)-th largest \(y\) in \([l, r)\)
Maybe Int
kthLargestIn wm l r k
| k < 0 || k >= r - l = Nothing
| 0 <= l && l < r && r <= lengthRwm wm = Just $ unsafeKthLargestIn wm l r k
| otherwise = Nothing
-- | \(O(\log |S|)\) Given an interval \([l, r)\), it returns both the index and the value of the
-- \(k\)-th (0-based) largest value. Note that duplicated values are counted as distinct occurrences.
--
-- @since 1.1.0.0
{-# INLINEABLE ikthLargestIn #-}
ikthLargestIn ::
-- | A wavelet matrix
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(k\)
Int ->
-- | \((i, y)\) for \(k\)-th largest \(y\) in \([l, r)\)
Maybe (Int, Int)
ikthLargestIn wm l r k
| k < 0 || k >= r - l = Nothing
| 0 <= l && l < r && r <= lengthRwm wm = Just $ unsafeIKthLargestIn wm l r k
| otherwise = Nothing
-- | \(O(\log |S|)\) Given an interval \([l, r)\), it returns the index of the \(k\)-th (0-based)
-- smallest value. Note that duplicated values are counted as distinct occurrences.
--
-- @since 1.1.0.0
{-# INLINEABLE kthSmallestIn #-}
kthSmallestIn ::
-- | A wavelet matrix
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(k\)
Int ->
-- | \(k\)-th largest \(y\) in \([l, r)\)
Maybe Int
kthSmallestIn wm l r k
| k < 0 || k >= r - l = Nothing
| 0 <= l && l < r && r <= lengthRwm wm = Just $ unsafeKthSmallestIn wm l r k
| otherwise = Nothing
-- | \(O(\log |S|)\) Given an interval \([l, r)\), it returns both the index and the value of the
-- \(k\)-th (0-based) smallest value. Note that duplicated values are counted as distinct occurrences.
--
-- @since 1.1.0.0
{-# INLINEABLE ikthSmallestIn #-}
ikthSmallestIn ::
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(k\)
Int ->
-- | \((i, y)\) for \(k\)-th largest \(y\) in \([l, r)\)
Maybe (Int, Int)
ikthSmallestIn wm l r k
| k < 0 || k >= r - l = Nothing
| 0 <= l && l < r && r <= lengthRwm wm = Just $ unsafeIKthSmallestIn wm l r k
| otherwise = Nothing
-- | \(O(\log a)\) Returns \(k\)-th (0-based) biggest number in \([l, r)\). Note that duplicated
-- values are counted as distinct occurrences.
--
-- @since 1.1.0.0
{-# INLINEABLE unsafeKthLargestIn #-}
unsafeKthLargestIn :: RawWaveletMatrix -> Int -> Int -> Int -> Int
unsafeKthLargestIn wm l r k = unsafeKthSmallestIn wm l r (r - l - (k + 1))
-- | \(O(\log a)\)
--
-- @since 1.1.0.0
{-# INLINEABLE unsafeIKthLargestIn #-}
unsafeIKthLargestIn :: RawWaveletMatrix -> Int -> Int -> Int -> (Int, Int)
unsafeIKthLargestIn wm l r k = unsafeIKthSmallestIn wm l r (r - l - (k + 1))
-- | \(O(\log a)\)
--
-- @since 1.1.0.0
{-# INLINEABLE unsafeKthSmallestIn #-}
unsafeKthSmallestIn :: RawWaveletMatrix -> Int -> Int -> Int -> Int
unsafeKthSmallestIn wm l_ r_ k_ =
let (!x, !_, !_, !_) = goDown wm l_ r_ k_
in x
-- | \(O(\log a)\)
--
-- @since 1.1.0.0
{-# INLINEABLE unsafeIKthSmallestIn #-}
unsafeIKthSmallestIn :: RawWaveletMatrix -> Int -> Int -> Int -> (Int, Int)
unsafeIKthSmallestIn wm l_ r_ k_ =
let (!x, !l, !_, !k) = goDown wm l_ r_ k_
!i' = fromJust $ goUp wm (l + k) x
in (i', x)
-- | \(O(\log |S|)\) Looks up the maximum \(y\) in \([l, r) \times (-\infty, y_0]\).
--
-- @since 1.1.0.0
{-# INLINEABLE lookupLE #-}
lookupLE ::
-- | A wavelet matrix
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(y_0\)
Int ->
-- | Maximum \(y\) in \([l, r) \times (-\infty, y_0]\)
Maybe Int
lookupLE wm l r x
| r' == l' = Nothing
| rank_ == 0 = Nothing
| otherwise = Just $ unsafeKthSmallestIn wm l' r' (rank_ - 1)
where
-- clamp
l' = max 0 l
r' = min (lengthRwm wm) r
rank_ = rankBetween wm l r minBound (x + 1)
-- | \(O(\log a)\) Finds the maximum \(x\) in \([l, r)\) s.t. \(x_{0} \lt x\).
--
-- @since 1.1.0.0
{-# INLINEABLE lookupLT #-}
lookupLT ::
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(x\)
Int ->
-- | Maximum \(y\) in \([l, r) \times (-\infty, y_0)\)
Maybe Int
lookupLT wm l r x = lookupLE wm l r (x - 1)
-- | \(O(\log |S|)\) Looks up the minimum \(y\) in \([l, r) \times [y_0, \infty)\).
--
-- @since 1.1.0.0
{-# INLINEABLE lookupGE #-}
lookupGE ::
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(y_0\)
Int ->
-- | Minimum \(y\) in \([l, r) \times [y_0, \infty)\).
Maybe Int
lookupGE wm l r x
| r' == l' = Nothing
| rank_ >= r' - l' = Nothing
| otherwise =
Just $ unsafeKthSmallestIn wm l' r' rank_
where
-- clamp
l' = max 0 l
r' = min (lengthRwm wm) r
rank_ = rankBetween wm l' r' minBound x
-- | \(O(\log |S|)\) Looks up the minimum \(y\) in \([l, r) \times (y_0, \infty)\).
--
-- @since 1.1.0.0
{-# INLINEABLE lookupGT #-}
lookupGT ::
RawWaveletMatrix ->
-- | \(l\)
Int ->
-- | \(r\)
Int ->
-- | \(y_0\)
Int ->
-- | Minimum \(y\) in \([l, r) \times (y_0, \infty)\)
Maybe Int
lookupGT wm l r x = lookupGE wm l r (x + 1)
-- | \(O(\min(|S|, L) \log |S|)\) Collects \((y, \mathrm{rank}(y))\) in range \([l, r)\) in
-- ascending order of \(y\). Note that it's only fast when the \(|S|\) is very small.
--
-- @since 1.1.0.0
{-# INLINEABLE assocsIn #-}
assocsIn :: RawWaveletMatrix -> Int -> Int -> [(Int, Int)]
assocsIn wm l r = assocsWith wm l r id
-- | \(O(\log A \min(|A|, L))\) Internal implementation of `assocs`.
--
-- @since 1.1.0.0
{-# INLINEABLE assocsWith #-}
assocsWith :: RawWaveletMatrix -> Int -> Int -> (Int -> Int) -> [(Int, Int)]
assocsWith RawWaveletMatrix {..} l_ r_ f
| l'_ < r'_ = inner (0 :: Int) (0 :: Int) l'_ r'_ []
| otherwise = []
where
-- clamp
l'_ = max 0 l_
r'_ = min lengthRwm r_
-- DFS. [l, r)
inner !acc iRow !l !r res
| iRow >= heightRwm =
let !n = r - l
!acc' = f acc
in (acc', n) : res
| otherwise = do
let !bits = bitsRwm VG.! iRow
!l0 = BV.rank0 bits l
!r0 = BV.rank0 bits r
!nZeros = nZerosRwm VG.! iRow
-- go right (visit bigger values first)
!l' = l + nZeros - l0
!r' = r + nZeros - r0
!res'
| l' < r' = inner (acc .|. bit (heightRwm - 1 - iRow)) (iRow + 1) l' r' res
| otherwise = res
!res''
-- go left
| l0 < r0 = inner acc (iRow + 1) l0 r0 res'
| otherwise = res'
in res''
-- | \(O(\min(|S|, L) \log |S|)\) Collects \((y, \mathrm{rank}(y))\) in range \([l, r)\) in
-- descending order of \(y\). Note that it's only fast when the \(|S|\) is very small.
--
-- @since 1.1.0.0
{-# INLINEABLE descAssocsIn #-}
descAssocsIn :: RawWaveletMatrix -> Int -> Int -> [(Int, Int)]
descAssocsIn wm l r = descAssocsInWith wm l r id
-- | \(O(\log A \min(|A|, L))\) Internal implementation of `descAssoc`.
--
-- @since 1.1.0.0
{-# INLINEABLE descAssocsInWith #-}
descAssocsInWith :: RawWaveletMatrix -> Int -> Int -> (Int -> Int) -> [(Int, Int)]
descAssocsInWith RawWaveletMatrix {..} l_ r_ f
| l'_ < r'_ = inner (0 :: Int) (0 :: Int) l'_ r'_ []
| otherwise = []
where
-- clamp
l'_ = max 0 l_
r'_ = min lengthRwm r_
-- DFS. [l, r)
inner !acc iRow !l !r res
| iRow >= heightRwm =
let !n = r - l
!acc' = f acc
in (acc', n) : res
| otherwise = do
let !bits = bitsRwm VG.! iRow
!l0 = BV.rank0 bits l
!r0 = BV.rank0 bits r
!nZeros = nZerosRwm VG.! iRow
!res'
-- go left
| l0 < r0 = inner acc (iRow + 1) l0 r0 res
| otherwise = res
-- go right (visit bigger values first)
!l' = l + nZeros - l0
!r' = r + nZeros - r0
!res''
| l' < r' = inner (acc .|. bit (heightRwm - 1 - iRow)) (iRow + 1) l' r' res'
| otherwise = res'
in res''