leetcode 1494并行课程2 状态压缩详解(贪心坑了多少人!)和子集的巧妙枚举(很好用!)

题目连接:并行课程2
题意:有n个有前后驱关系的课程,学习一门课程的前提是在前一个学期学完这个课程的所有前驱课程,一个学期最多可学k门课程,问学完所有的课程最少需要多少个学期?
刚开始做的时候入了这道题很大的一个坑,想用拓扑排序和贪心做,我看评论区也有很多跟我一样的,有根据深度贪心的,也有根据长度贪心的,不过大家都很积极都举出了反例,现在我来详解一个这道题的状态压缩写法:
大致思路:状态很明显,用一个二进制数表示当前已经学过的课程,0为没学过,1为学过,那么对于当前状态state,首先算出在当前状态下我们可以学习的课程,这里真的很巧妙,我们继续用一个二进制数next来表示我们能继续学习的课程,同样的,0为能学,1为不能学,然后我们就可以枚举这个next的子集,设c[next]为next中1的个数,那么next的子集就有2的c[next] - 1次方个(每一个1都有两种情况,选与不选)。
详细写法:
首先我们预处理一个每个二进制数的1的个数:

        vector<int> c(1<<n, 0)for (int i = 0; i < (1<<n); i++) 
            for (int j = 0; j < n; j++)
                if (i>>j&1) c[i]++;

然后就是我觉得很巧妙的地方了,我们还用一个二进制数来表示要学习第i个课程需要学习的课程:
pre[i]就表示学习i之前要学习的课程的状态压缩后的二进制数。

vector<int> pre(20, 0);
for (auto &c : dependencies) pre[c[1] - 1] |= (1<<(c[0] - 1));

于是我们可以开始推状态转移公式:
设当前状态为state
在推公式的时候我们可以贪心一点,每一个学期,能多学点就多学点。
如果c[next] <= k那么 dp[state|next] = min(dp[state|next], dp[state] + 1);
如果c[next] > k,那么我们就枚举next的子集,这里我学到一个非常好用,简单而且特别快的枚举一个二进制数的所有子集的方法:

                for (int j = next; j; j = (j - 1)&next) {
                    if (c[j] == k)
                        dp[state|j] = min(dp[state|j], dp[state] + 1);
                }

这里我仅仅只在c[j] == k的时候才进行状态转移,因为我贪心的想一个学期多学一点课。
细节处理:
由于是取的最小值,所有我们要预处理dp为最大值:

        for (int i = 0; i < (1<<n); i++) dp[i] = n;
        dp[0] = 0;

注意这里要把dp[0]设为0,我一开始就是没注意,结果错了。
最后附上完整 代码:

class Solution {
public:
    int minNumberOfSemesters(int n, vector<vector<int>>& dependencies, int k) {
        vector<int> c(1<<n, 0), pre(20, 0), dp(1<<n);
        for (int i = 0; i < (1<<n); i++) 
            for (int j = 0; j < n; j++)
                if (i>>j&1) c[i]++;
        for (int i = 0; i < (1<<n); i++) dp[i] = n;
        dp[0] = 0;
        for (auto &c : dependencies) pre[c[1] - 1] |= (1<<(c[0] - 1));
        for (int state = 0; state < (1<<n); state++) {
            int next = 0;
            for (int i = 0; i < n; i++) 
                if ((pre[i]&state) == pre[i]) next |= (1<<i);
            next &= (~state);
            if (c[next] <= k) dp[state|next] = min(dp[state|next], dp[state] + 1);
            else {
                for (int j = next; j; j = (j - 1)&next) {
                    if (c[j] == k)
                        dp[state|j] = min(dp[state|j], dp[state] + 1);
                }
           }
        }
        return dp[(1<<n) - 1];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值