Skip to main content
Back to problems
#3594
Hard Algorithms

Minimum time to transport all individuals

Array Dynamic Programming Bit Manipulation Graph Theory Heap (Priority Queue) Shortest Path Bitmask
28.1% acceptance
Feb 25, 2026
41
6
You are given n individuals, boat capacity k, m cyclic stages with multipliers mul[j]. Groups cross in time = max(time[group]) * mul[stage]. Stage advances by floor(crossing_time) % m. One person returns if individuals remain. Return min total time. -1 if impossible.

Solution

Rust
Time O(n * m)
Space O(n * m)
LeetCode
solution.rs
impl Solution {
  pub fn min_time(n: i32, k: i32, m: i32, time: Vec<i32>, mul: Vec<f64>) -> f64 {
    let n = n as usize;
    let k = k as usize;
    let m = m as usize;

    // k == 1 and n > 1: impossible – sender must also return, making no progress
    if k == 1 && n > 1 {
      return -1.0;
    }

    // Dijkstra on state (mask, stage):
    //   mask  – bit i set iff person i is already at the destination
    //   stage – current cyclic stage index (0..m-1)
    // Each transition = one forward crossing (group ≤ k) + optional return trip.
    // The boat is always at base at the start of every state so the pairing is valid.
    //
    // Distances are stored as i64-scaled integers (scale = 10^9) to avoid the
    // float→int64→float round-trip precision loss that corrupts the pruning check.

    use std::collections::BinaryHeap;
    use std::cmp::Reverse;

    let full_mask = (1usize << n) - 1;
    // scale: 10^9 gives nanosecond precision; max time ≈ 100*2.0*12 trips ≈ 2400 → well within i64
    const SCALE: i64 = 1_000_000_000;
    let mut dist = vec![vec![i64::MAX; m]; 1 << n];
    dist[0][0] = 0;

    let mut heap: BinaryHeap<(Reverse<i64>, usize, usize)> = BinaryHeap::new();
    heap.push((Reverse(0i64), 0usize, 0usize));

    while let Some((Reverse(t_scaled), mask, stage)) = heap.pop() {
      if mask == full_mask {
        return t_scaled as f64 / SCALE as f64;
      }
      // Stale entry – a shorter path to (mask, stage) was already processed
      if t_scaled > dist[mask][stage] { continue; }

      // People still at base camp (bit not set in mask)
      let base_mask: usize = (0..n)
        .filter(|&i| (mask >> i) & 1 == 0)
        .fold(0usize, |acc, i| acc | (1 << i));

      if base_mask == 0 { return t_scaled as f64 / SCALE as f64; }

      // Enumerate every non-empty subset of base people with size ≤ k
      let mut sub = base_mask;
      loop {
        if sub != 0 && sub.count_ones() as usize <= k {
          let max_t = (0..n)
            .filter(|&i| (sub >> i) & 1 == 1)
            .map(|i| time[i])
            .max()
            .unwrap() as f64;

          let cross_t    = max_t * mul[stage];
          let cross_sc   = (cross_t * SCALE as f64) as i64;
          let new_stage  = (stage + cross_t.floor() as usize) % m;
          let new_mask   = mask | sub;

          if new_mask == full_mask {
            // Last trip – no return needed
            let nt = t_scaled + cross_sc;
            if nt < dist[new_mask][new_stage] {
              dist[new_mask][new_stage] = nt;
              heap.push((Reverse(nt), new_mask, new_stage));
            }
          } else {
            // Must send someone back; try every person currently at destination
            let at_dest_mask = new_mask; // bits set = people at dest
            for r in 0..n {
              if (at_dest_mask >> r) & 1 == 0 { continue; }
              let ret_t     = time[r] as f64 * mul[new_stage];
              let ret_sc    = (ret_t * SCALE as f64) as i64;
              let ret_stage = (new_stage + ret_t.floor() as usize) % m;
              let fin_mask  = new_mask & !(1 << r);
              let total     = t_scaled + cross_sc + ret_sc;
              if total < dist[fin_mask][ret_stage] {
                dist[fin_mask][ret_stage] = total;
                heap.push((Reverse(total), fin_mask, ret_stage));
              }
            }
          }
        }
        if sub == 0 { break; }
        sub = (sub - 1) & base_mask;
      }
    }

    // If full_mask was never reached, return -1
    let best = dist[full_mask].iter().cloned().filter(|&v| v < i64::MAX).min();
    match best {
      Some(v) => v as f64 / SCALE as f64,
      None    => -1.0,
    }
  }
}