Skip to content

Commit eaf895d

Browse files
feat: add task assignment problem using bitmasking and DP (#958)
1 parent 1ce9f3a commit eaf895d

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
* [Rod Cutting](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/rod_cutting.rs)
106106
* [Snail](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/snail.rs)
107107
* [Subset Generation](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/subset_generation.rs)
108+
* [Task Assignment](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/task_assignment.rs)
108109
* [Subset Sum](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/subset_sum.rs)
109110
* [Trapped Rainwater](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/trapped_rainwater.rs)
110111
* [Word Break](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/word_break.rs)

src/dynamic_programming/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod rod_cutting;
1717
mod snail;
1818
mod subset_generation;
1919
mod subset_sum;
20+
mod task_assignment;
2021
mod trapped_rainwater;
2122
mod word_break;
2223

@@ -47,5 +48,6 @@ pub use self::rod_cutting::rod_cut;
4748
pub use self::snail::snail;
4849
pub use self::subset_generation::list_subset;
4950
pub use self::subset_sum::is_sum_subset;
51+
pub use self::task_assignment::count_task_assignments;
5052
pub use self::trapped_rainwater::trapped_rainwater;
5153
pub use self::word_break::word_break;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Task Assignment Problem using Bitmasking and DP in Rust
2+
// Time Complexity: O(2^M * N) where M is number of people and N is number of tasks
3+
// Space Complexity: O(2^M * N) for the DP table
4+
5+
use std::collections::HashMap;
6+
7+
/// Solves the task assignment problem where each person can do only certain tasks,
8+
/// each person can do only one task, and each task is performed by only one person.
9+
/// Uses bitmasking and dynamic programming to count total number of valid assignments.
10+
///
11+
/// # Arguments
12+
/// * `task_performed` - A vector of vectors where each inner vector contains tasks
13+
/// that a person can perform (1-indexed task numbers)
14+
/// * `total_tasks` - The total number of tasks (N)
15+
///
16+
/// # Returns
17+
/// * The total number of valid task assignments
18+
pub fn count_task_assignments(task_performed: Vec<Vec<usize>>, total_tasks: usize) -> i64 {
19+
let num_people = task_performed.len();
20+
let dp_size = 1 << num_people;
21+
22+
// Initialize DP table with -1 (uncomputed)
23+
let mut dp = vec![vec![-1; total_tasks + 2]; dp_size];
24+
25+
let mut task_map = HashMap::new();
26+
let final_mask = (1 << num_people) - 1;
27+
28+
// Build the task -> people mapping
29+
for (person, tasks) in task_performed.iter().enumerate() {
30+
for &task in tasks {
31+
task_map.entry(task).or_insert_with(Vec::new).push(person);
32+
}
33+
}
34+
35+
// Recursive DP function
36+
fn count_ways_until(
37+
dp: &mut Vec<Vec<i64>>,
38+
task_map: &HashMap<usize, Vec<usize>>,
39+
final_mask: usize,
40+
total_tasks: usize,
41+
mask: usize,
42+
task_no: usize,
43+
) -> i64 {
44+
// Base case: all people have been assigned tasks
45+
if mask == final_mask {
46+
return 1;
47+
}
48+
49+
// Base case: no more tasks available but not all people assigned
50+
if task_no > total_tasks {
51+
return 0;
52+
}
53+
54+
// Return cached result if already computed
55+
if dp[mask][task_no] != -1 {
56+
return dp[mask][task_no];
57+
}
58+
59+
// Option 1: Skip the current task
60+
let mut total_ways =
61+
count_ways_until(dp, task_map, final_mask, total_tasks, mask, task_no + 1);
62+
63+
// Option 2: Assign current task to a capable person who isn't busy
64+
if let Some(people) = task_map.get(&task_no) {
65+
for &person in people {
66+
// Check if this person is already assigned a task
67+
if mask & (1 << person) != 0 {
68+
continue;
69+
}
70+
71+
// Assign task to this person and recurse
72+
total_ways += count_ways_until(
73+
dp,
74+
task_map,
75+
final_mask,
76+
total_tasks,
77+
mask | (1 << person),
78+
task_no + 1,
79+
);
80+
}
81+
}
82+
83+
// Cache the result
84+
dp[mask][task_no] = total_ways;
85+
total_ways
86+
}
87+
88+
// Start recursion with no people assigned and first task
89+
count_ways_until(&mut dp, &task_map, final_mask, total_tasks, 0, 1)
90+
}
91+
92+
#[cfg(test)]
93+
mod tests {
94+
use super::*;
95+
96+
// Macro to generate multiple test cases for the task assignment function
97+
macro_rules! task_assignment_tests {
98+
($($name:ident: $input:expr => $expected:expr,)*) => {
99+
$(
100+
#[test]
101+
fn $name() {
102+
let (task_performed, total_tasks) = $input;
103+
assert_eq!(count_task_assignments(task_performed, total_tasks), $expected);
104+
}
105+
)*
106+
};
107+
}
108+
109+
task_assignment_tests! {
110+
test_case_1: (vec![vec![1, 3, 4], vec![1, 2, 5], vec![3, 4]], 5) => 10,
111+
test_case_2: (vec![vec![1, 2], vec![1, 2]], 2) => 2,
112+
test_case_3: (vec![vec![1], vec![2], vec![3]], 3) => 1,
113+
test_case_4: (vec![vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], 3) => 6,
114+
test_case_5: (vec![vec![1], vec![1]], 1) => 0,
115+
116+
// Edge test case
117+
test_case_single_person: (vec![vec![1, 2, 3]], 3) => 3,
118+
}
119+
}

0 commit comments

Comments
 (0)