并行课程II
给你一个整数 n 表示某所大学里课程的数目,编号为 1 到 n ,数组 relations 中, relations[i] = [xi, yi] 表示一个先修课的关系,也就是课程 xi 必须在课程 yi 之前上。同时你还有一个整数 k 。
在一个学期中,你 最多 可以同时上 k 门课,前提是这些课的先修课在之前的学期里已经上过了。
请你返回上完所有课最少需要多少个学期。题目保证一定存在一种上完所有课的方式。
1. 状态压缩 + 动态规划
需要记录每个状态的先修课程
然后根据不需要先修的课程状态进行转移
也就是,每一次从一个完整的拓扑排序,转移到另一个
class Solution {
public:
int minNumberOfSemesters(int n, vector<vector<int>>& relations, int k) {
//求最优使用动态规划
vector<int> dp(1 << n, INT_MAX); //记录所有状态的最优值,用于剪枝
vector<int> need(1 << n, 0);
for (auto& edge : relations) { //记录对应节点的前置节点
need[(1 << (edge[1] - 1))] |= 1 << (edge[0] - 1);
}
dp[0] = 0; //初始没有任何前节点消去,花费为学期为0
for (int i = 1; i < (1 << n); ++i) { //遍历所有状态
need[i] = need[i & (i - 1)] | need[i & (-i)]; // 计算状态i所需的先修课程
if ((need[i] | i) != i) { // i 中有任意一门课程的前置课程没有完成学习
continue;
}
//此时状态i是一个完备独立的集合,满足拓扑排序,计算该状态下的最小学期
int valid = i ^ need[i]; // 当前学期可以进行学习的课程集合
if (__builtin_popcount(valid) <= k) { // 如果个数小于 k,则可以全部学习,不再枚举子集
dp[i] = min(dp[i], dp[i ^ valid] + 1);
} else { // 否则枚举当前学期需要进行学习的课程集合
for (int sub = valid; sub; sub = (sub - 1) & valid) {
if (__builtin_popcount(sub) <= k) {
dp[i] = min(dp[i], dp[i ^ sub] + 1);
}
}
}
}
return dp[(1 << n) - 1];
}
};