Description Link to heading
1494. Parallel Courses II (Hard)
Solution Link to heading
This problem readily brings to mind the concept of topological sorting, followed by a greedy approach. However, this approach is erroneous! In essence, this problem is NP-Hard and can only be solved through brute force.
Let’s consider the application of state-compressed dynamic programming (DP). The subproblems are relatively straightforward to identify. We start by choosing $1, 2$, followed by selecting $3, 4, 5 $(meeting the prerequisites). We can use a binary number, $to$$study$, to represent the set of courses we currently need to study, and $pre[j]$ to represent the set of prerequisite courses for course $j$. The recursive function can be denoted as $dfs(to$$study, pre)$.
Within this recursive function, we first calculate the set of courses $tmp$ that can be studied at the moment:
for (int i = 0; i < n; ++i) {
if ((((1 << i) & to_study) != 0) && (pre[i] & to_study) == 0) {
// Indicates that i is in to_study and its prerequisite courses have been studied (or it has no prerequisites)
tmp = (tmp | (1 << i));
++cnt;
}
}
If the number of courses $cnt$ in $tmp$ is less than or equal to k, we can directly study all the courses in the $tmp$ set. Otherwise, we iterate through the subsets of $tmp$. If the number of courses $m$ in subset $i$ satisfies $m <= k$, we study that subset. In the next recursive call, the courses to be studied can be represented as $to$_$study \oplus i$.
For a method to enumerate subsets, refer to Bitwise Operations and Sets.
In C++, the library function to count the number of $1$ bits in a binary number is __builtin_popcount(i)
.
Code Link to heading
class Solution {
public:
static auto dfs(int to_study, vector<int> &cache, int full, vector<int> &pre, int n, int k) -> int {
if (to_study == 0) {
return 0;
}
if (cache[to_study] != -1) {
return cache[to_study];
}
int studied = (to_study ^ full);
int count = 0; // Count the number of courses that can be studied
int tmp = 0; // Courses to be studied in this recursive call
for (int i = 0; i < n; ++i) {
if ((((1 << i) & to_study) != 0) && (pre[i] & to_study) == 0) {
// Indicates that i is in to_study and its prerequisite courses have been studied (or it has no prerequisites)
tmp = (tmp | (1 << i));
++count;
}
}
if (count <= k) {
cache[to_study] = dfs(to_study ^ tmp, cache, full, pre, n, k) + 1;
return cache[to_study];
}
int result = INT_MAX;
for (int i = tmp; i != 0; i = (i - 1) & tmp) {
if (__builtin_popcount(i) <= k) {
result = min(result, dfs(to_study ^ i, cache, full, pre, n, k) + 1);
}
}
cache[to_study] = result;
return cache[to_study];
}
int minNumberOfSemesters(int n, vector<vector<int>> &relations, int k) {
vector<int> pre(n);
for (auto &vec : relations) {
int x = vec[0] - 1, y = vec[1] - 1; // Adjusting indices to start from 0 to n-1
pre[y] = (pre[y] | (1 << x));
}
int full = (1 << n) - 1; // Full set
vector<int> cache(1 << n, -1);
return dfs(full, cache, full, pre, n, k);
}
};