并行课程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];
    }
};
posted @ 2023-07-05 01:41  失控D大白兔  阅读(27)  评论(0编辑  收藏  举报