ac-library-hs-1.2.3.0: src/AtCoder/Extra/KdTree.hs
{-# LANGUAGE RecordWildCards #-}
-- | Static, \(k\)-dimensional tree \((k = 2)\).
--
-- - Points are fixed on `build`.
-- - Multiple points can exist at the same coordinate.
--
-- ==== __Examples__
-- >>> import AtCoder.Extra.KdTree qualified as KT
-- >>> import Data.Vector.Unboxed qualified as VU
-- >>> let xys = VU.fromList [(0, 0), (1, 1), (4, 2)]
-- >>> let kt = KT.build2 xys
-- >>> -- Find point indices in [0, 2) x [0, 2) with maximum capacity 3
-- >>> KT.findPointsIn kt 0 2 0 2 3
-- [0,1]
--
-- >>> KT.findNearestPoint kt 3 3
-- Just 2
--
-- @since 1.2.2.0
module AtCoder.Extra.KdTree
( -- * K-dimensional tree
KdTree (..),
-- * Constructors
build,
build2,
findPointsIn,
findNearestPoint,
)
where
import AtCoder.Internal.Assert qualified as ACIA
import AtCoder.Internal.Bit qualified as ACIB
import Control.Monad.ST (runST)
import Data.Bits
import Data.Ord (comparing)
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)
-- | Static, \(k\)-dimensional tree \((k = 2)\).
--
-- @since 1.2.2.0
data KdTree = KdTree
{ -- | The number of points in the \(k\)-d tree.
--
-- @since 1.2.2.0
nKt :: {-# UNPACK #-} !Int,
-- | Rectangle information: inclusive (closed) ranges \([x_1, x_2) \times [y_1, y_2)\).
--
-- @since 1.2.2.0
incRectsKt :: !(VU.Vector (Int, Int, Int, Int)),
-- | Maps rectangle index to original point index.
--
-- @since 1.2.2.0
dataKt :: !(VU.Vector Int)
}
-- | \(O(n \log n)\) Creates a `KdTree` from \(x\) and \(y\) vectors.
--
-- ==== Constraints
-- - \(|\mathrm{xs}| = |\mathrm{ys}|\).
--
-- @since 1.2.2.0
{-# INLINEABLE build #-}
build ::
(HasCallStack) =>
-- | \(x\) coordnates
VU.Vector Int ->
-- | \(y\) coordnates
VU.Vector Int ->
-- | `KdTree`
KdTree
build xs0 ys0 =
let nKt = VU.length xs0
!_ = ACIA.runtimeAssert (nKt == VU.length ys0) "AtCoder.Extra.KdTree.buildST: the length of `xs`, `ys` and `vs` must be equal"
in if nKt == 0
then KdTree 0 VU.empty VU.empty
else runST $ do
let vs0 = VU.generate nKt id
let logKt = countTrailingZeros $ ACIB.bitCeil (nKt + 1)
dat <- VUM.replicate (bit (logKt + 1)) (-1 :: Int)
incRectsVec <- VUM.replicate (bit (logKt + 1)) (maxBound, minBound, maxBound, minBound)
let VUM.MV_4 _ xMins xMaxes yMins yMaxes = incRectsVec
-- - idx: rectangle index (one-based)
-- - xs, ys, vs: point information (x, y and monoid value)
-- - ids: maps sorted vertices to the original vertex indices
-- - divX: represents hyperplane direction for point partition
let -- buildSubtree :: Int -> VU.Vector Int -> VU.Vector Int -> VU.Vector Int -> VU.Vector Int -> Bool -> ST s ()
buildSubtree idx xs ys vs ids divX = do
let n = VU.length xs
-- retrieve the bounds:
let (!xMin, !xMax, !yMin, !yMax) =
VU.foldl'
(\(!a, !b, !c, !d) (!x, !y) -> (min a x, max b x, min c y, max d y))
(maxBound, minBound, maxBound, minBound)
$ VU.zip xs ys
VGM.modify xMins (min xMin) idx
VGM.modify xMaxes (max xMax) idx
VGM.modify yMins (min yMin) idx
VGM.modify yMaxes (max yMax) idx
if n == 1
then do
-- it's a terminal
VGM.write dat idx $ vs VG.! 0
else do
-- partition the vertices into two:
let m = n `div` 2
let is = VU.create $ do
vec <- VUM.generate n id
if divX
then VAI.selectBy (comparing (xs VG.!)) vec m
else VAI.selectBy (comparing (ys VG.!)) vec m
pure vec
-- TODO: permute in-place?
let (!xsL, !xsR) = VG.splitAt m $ VG.backpermute xs is
let (!ysL, !ysR) = VG.splitAt m $ VG.backpermute ys is
let (!vsL, !vsR) = VG.splitAt m $ VG.backpermute vs is
let (!idsL, !idsR) = VG.splitAt m $ VG.backpermute ids is
-- build the subtree:
buildSubtree (2 * idx + 0) xsL ysL vsL idsL (not divX)
buildSubtree (2 * idx + 1) xsR ysR vsR idsR (not divX)
buildSubtree 1 xs0 ys0 vs0 (VU.generate nKt id) True
dataKt <- VU.unsafeFreeze dat
incRectsKt <- VU.unsafeFreeze incRectsVec
pure KdTree {..}
-- | \(O(n \log n)\) Creates `KdTree` from a \((x, y)\) vector.
--
-- ==== Constraints
-- - \(|\mathrm{xs}| = |\mathrm{ys}|\).
--
-- @since 1.2.2.0
{-# INLINE build2 #-}
build2 ::
(HasCallStack) =>
-- | \(x, y\) coordnates
VU.Vector (Int, Int) ->
-- | `KdTree`
KdTree
build2 xys = build xs ys
where
(!xs, !ys) = VU.unzip xys
-- | \(O(n)\) Collects points in \([x_1, x_2) \times [y_1, y_2)\).
--
-- @since 1.2.2.0
{-# INLINEABLE findPointsIn #-}
findPointsIn ::
(HasCallStack) =>
-- | `KdTree`
KdTree ->
-- | \(x_1\)
Int ->
-- | \(x_2\)
Int ->
-- | \(y_1\)
Int ->
-- | \(y_2\)
Int ->
-- | Maximum number of points in \([x_1, x_2) \times [y_1, y_2)\).
Int ->
-- | Point indices in \([x_1, x_2) \times [y_1, y_2)\).
VU.Vector Int
findPointsIn KdTree {..} x1 x2 y1 y2 capacity
| nKt == 0 = VU.empty
| otherwise = runST $ do
res <- VUM.unsafeNew $ min nKt capacity
let inner i iPush
-- not intersected
| x2 <= xMin || xMax < x1 = pure iPush
| y2 <= yMin || yMax < y1 = pure iPush
-- a leaf
| vi /= -1 = do
VGM.write res iPush vi
pure $ iPush + 1
-- a parental rectangle area
| otherwise = do
iPush' <- inner (2 * i + 0) iPush
inner (2 * i + 1) iPush'
where
(!xMin, !xMax, !yMin, !yMax) = incRectsKt VG.! i
vi = dataKt VG.! i
n <- inner 1 0
VU.take n <$> VU.unsafeFreeze res
where
!_ = ACIA.runtimeAssert (x1 <= x2 && y1 <= y2) "AtCoder.Extra.KdTree.findPointsIn: given invalid interval"
-- | \(O(\log n)\), only if the points are randomly distributed. Returns the index of the nearest
-- point, or `Nothing` if the `KdTree` has no point.
--
-- @since 1.2.2.0
{-# INLINEABLE findNearestPoint #-}
findNearestPoint ::
(HasCallStack) =>
-- | `KdTree`
KdTree ->
-- | \(x\)
Int ->
-- | \(y\)
Int ->
-- | The nearest point index
Maybe Int
findNearestPoint KdTree {..} x y
| nKt == 0 = Nothing
| otherwise = Just . fst $! inner 1 {- FIXME: -} (-1, -1)
where
clamp a aMin aMax = min aMax $ max a aMin
-- Used for pruning. It's |(x, y)|^2 if the (x, y) is within the rectangle.
bestDistSquared i =
let (!xMin, !xMax, !yMin, !yMax) = incRectsKt VG.! i
dx = x - clamp x xMin xMax
dy = y - clamp y yMin yMax
in dx * dx + dy * dy
-- returns (index, bestDist)
inner i res@(!resV, !resD)
-- pruning (we have a better point than any point in this rectangle)
| resV /= -1 && resD <= d = res
-- it's a leaf
| dataI /= -1 = (dataI, d)
-- look into the children
| d0 < d1 = inner (2 * i + 0) $ inner (2 * i + 1) res
| otherwise = inner (2 * i + 1) $ inner (2 * i + 0) res
where
d = bestDistSquared i
dataI = dataKt VG.! i
d0 = bestDistSquared (2 * i + 0)
d1 = bestDistSquared (2 * i + 0)