Leetcode 1494

并行课程

给你一个整数 n 表示某所大学里课程的数目,编号为 1 到 n ,数组 dependencies 中, dependencies[i] = [xi, yi]  表示一个先修课的关系,也就是课程 xi 必须在课程 yi 之前上。同时你还有一个整数 k 。

在一个学期中,你 最多 可以同时上 k 门课,前提是这些课的先修课在之前的学期里已经上过了。

请你返回上完所有课最少需要多少个学期。题目保证一定存在一种上完所有课的方式。

思路

刚开始认为是拓扑排序,但发现对于度相同的情况,先选择哪一个没有正确的贪心方案。因此本题实质上是一个状态压缩dp
最终的maks=(1<<n)-1,该状态应由可行状态一步步转移而来。什么是可行状态呢?首先是一个状态1的个数不超过k,二是每个状态s中1代表的先修课程不能出现在状态s中。
此外,每次转移过程,可行状态都是mask的一个子集。于是代码如下:

class Solution {
public:
    const int INF=0x3f3f3f3f;
    int minNumberOfSemesters(int n, vector<vector<int>>& dependencies, int k) {
        vector<int>pre(n);
        for(auto dep:dependencies)  //课程y的先修课程
            pre[dep[1]-1]|=(1<<(dep[0]-1));
        vector<int>dp(1<<n,INF),set(1<<n);
        vector<int>valid(1<<n);
        for(int i=0;i<(1<<n);i++)//枚举所有状态
        {
            if(__builtin_popcount(i)<=k){  //寻找可行状态并记录其所有的先修课程
                for(int j=0;j<n;j++){
                    if(i&(1<<j))  //i中存在状态j
                        set[i]|=pre[j]; //状态i所需要修的所有先修课程
                }
                valid[i]=((i&set[i])==0);  //要保证状态i不会和他的先修课程存在重合
            } 
        }
        dp[0]=0;
        for(int mask=0;mask<(1<<n);mask++)
        {
            for(int subset=mask;subset;subset=mask&(subset-1))  //当前正在进行的状态是subset,下一状态是mask,状态由[mask^subset]转移而来,subset状态是mask的子集,for循环体现了一种遍历mask所有子集的过程
            {
                if(valid[subset]&&((mask&set[subset])==set[subset])){
                    dp[mask]=min(dp[mask],dp[mask^subset]+1);
                }
            }
        }
        return dp[(1<<n)-1];
    }
};
posted @ 2020-10-04 16:09  blueattack  阅读(190)  评论(0编辑  收藏  举报