From 8bacaef11ea47501e2ddb9d21621ab8e00aed873 Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi Date: Tue, 25 Nov 2025 20:43:51 -0800 Subject: [PATCH 1/3] feat: add task assignment problem using bitmasking and DP --- DIRECTORY.md | 1 + src/dynamic_programming/mod.rs | 2 + src/dynamic_programming/task_assignment.rs | 119 +++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/dynamic_programming/task_assignment.rs diff --git a/DIRECTORY.md b/DIRECTORY.md index 46bb2a3af9f..372245624cf 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -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) * [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) * Financial diff --git a/src/dynamic_programming/mod.rs b/src/dynamic_programming/mod.rs index f18c1847479..47d9dae8789 100644 --- a/src/dynamic_programming/mod.rs +++ b/src/dynamic_programming/mod.rs @@ -16,6 +16,7 @@ mod optimal_bst; mod rod_cutting; mod snail; mod subset_generation; +mod task_assignment; mod trapped_rainwater; mod word_break; @@ -45,5 +46,6 @@ pub use self::optimal_bst::optimal_search_tree; pub use self::rod_cutting::rod_cut; pub use self::snail::snail; pub use self::subset_generation::list_subset; +pub use self::task_assignment::count_task_assignments; pub use self::trapped_rainwater::trapped_rainwater; pub use self::word_break::word_break; diff --git a/src/dynamic_programming/task_assignment.rs b/src/dynamic_programming/task_assignment.rs new file mode 100644 index 00000000000..385a39fb74f --- /dev/null +++ b/src/dynamic_programming/task_assignment.rs @@ -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>, 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>, + task_map: &HashMap>, + 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, + } +} From 5045ae0c72380c9dd0104a01c3fe7c8b9a7e5ebd Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:41:50 -0800 Subject: [PATCH 2/3] Update mod.rs --- src/dynamic_programming/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dynamic_programming/mod.rs b/src/dynamic_programming/mod.rs index 3daabfe9c10..b9674fc8cb0 100644 --- a/src/dynamic_programming/mod.rs +++ b/src/dynamic_programming/mod.rs @@ -47,7 +47,7 @@ pub use self::optimal_bst::optimal_search_tree; pub use self::rod_cutting::rod_cut; pub use self::snail::snail; pub use self::subset_generation::list_subset; -pub use self::task_assignment::count_task_assignments; 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; From 98ca0617e215734330ec35e884a686aa069cd7cd Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:42:23 -0800 Subject: [PATCH 3/3] Update mod.rs --- src/dynamic_programming/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dynamic_programming/mod.rs b/src/dynamic_programming/mod.rs index b9674fc8cb0..b3dc169201a 100644 --- a/src/dynamic_programming/mod.rs +++ b/src/dynamic_programming/mod.rs @@ -16,8 +16,8 @@ mod optimal_bst; mod rod_cutting; mod snail; mod subset_generation; -mod task_assignment; mod subset_sum; +mod task_assignment; mod trapped_rainwater; mod word_break;