Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
* [Rod Cutting](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/rod_cutting.rs)
* [Snail](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/snail.rs)
* [Subset Generation](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/subset_generation.rs)
* [Task Assignment](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/task_assignment.rs)
* [Subset Sum](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/subset_sum.rs)
* [Trapped Rainwater](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/trapped_rainwater.rs)
* [Word Break](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/word_break.rs)
Expand Down
2 changes: 2 additions & 0 deletions src/dynamic_programming/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod rod_cutting;
mod snail;
mod subset_generation;
mod subset_sum;
mod task_assignment;
mod trapped_rainwater;
mod word_break;

Expand Down Expand Up @@ -47,5 +48,6 @@ pub use self::rod_cutting::rod_cut;
pub use self::snail::snail;
pub use self::subset_generation::list_subset;
pub use self::subset_sum::is_sum_subset;
pub use self::task_assignment::count_task_assignments;
pub use self::trapped_rainwater::trapped_rainwater;
pub use self::word_break::word_break;
119 changes: 119 additions & 0 deletions src/dynamic_programming/task_assignment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Task Assignment Problem using Bitmasking and DP in Rust
// Time Complexity: O(2^M * N) where M is number of people and N is number of tasks
// Space Complexity: O(2^M * N) for the DP table

use std::collections::HashMap;

/// Solves the task assignment problem where each person can do only certain tasks,
/// each person can do only one task, and each task is performed by only one person.
/// Uses bitmasking and dynamic programming to count total number of valid assignments.
///
/// # Arguments
/// * `task_performed` - A vector of vectors where each inner vector contains tasks
/// that a person can perform (1-indexed task numbers)
/// * `total_tasks` - The total number of tasks (N)
///
/// # Returns
/// * The total number of valid task assignments
pub fn count_task_assignments(task_performed: Vec<Vec<usize>>, total_tasks: usize) -> i64 {
let num_people = task_performed.len();
let dp_size = 1 << num_people;

// Initialize DP table with -1 (uncomputed)
let mut dp = vec![vec![-1; total_tasks + 2]; dp_size];

let mut task_map = HashMap::new();
let final_mask = (1 << num_people) - 1;

// Build the task -> people mapping
for (person, tasks) in task_performed.iter().enumerate() {
for &task in tasks {
task_map.entry(task).or_insert_with(Vec::new).push(person);
}
}

// Recursive DP function
fn count_ways_until(
dp: &mut Vec<Vec<i64>>,
task_map: &HashMap<usize, Vec<usize>>,
final_mask: usize,
total_tasks: usize,
mask: usize,
task_no: usize,
) -> i64 {
// Base case: all people have been assigned tasks
if mask == final_mask {
return 1;
}

// Base case: no more tasks available but not all people assigned
if task_no > total_tasks {
return 0;
}

// Return cached result if already computed
if dp[mask][task_no] != -1 {
return dp[mask][task_no];
}

// Option 1: Skip the current task
let mut total_ways =
count_ways_until(dp, task_map, final_mask, total_tasks, mask, task_no + 1);

// Option 2: Assign current task to a capable person who isn't busy
if let Some(people) = task_map.get(&task_no) {
for &person in people {
// Check if this person is already assigned a task
if mask & (1 << person) != 0 {
continue;
}

// Assign task to this person and recurse
total_ways += count_ways_until(
dp,
task_map,
final_mask,
total_tasks,
mask | (1 << person),
task_no + 1,
);
}
}

// Cache the result
dp[mask][task_no] = total_ways;
total_ways
}

// Start recursion with no people assigned and first task
count_ways_until(&mut dp, &task_map, final_mask, total_tasks, 0, 1)
}

#[cfg(test)]
mod tests {
use super::*;

// Macro to generate multiple test cases for the task assignment function
macro_rules! task_assignment_tests {
($($name:ident: $input:expr => $expected:expr,)*) => {
$(
#[test]
fn $name() {
let (task_performed, total_tasks) = $input;
assert_eq!(count_task_assignments(task_performed, total_tasks), $expected);
}
)*
};
}

task_assignment_tests! {
test_case_1: (vec![vec![1, 3, 4], vec![1, 2, 5], vec![3, 4]], 5) => 10,
test_case_2: (vec![vec![1, 2], vec![1, 2]], 2) => 2,
test_case_3: (vec![vec![1], vec![2], vec![3]], 3) => 1,
test_case_4: (vec![vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], 3) => 6,
test_case_5: (vec![vec![1], vec![1]], 1) => 0,

// Edge test case
test_case_single_person: (vec![vec![1, 2, 3]], 3) => 3,
}
}