Loading

2023.6.16 并行课程 II

image

想当难的一道状压dp题。
首先这道题拓扑排序是错误的,因为拓扑只能保证找到选出一种上完所有课的方式,而不能保证选出的方式是所有方式中需要学期数最少的。
当然可以通过遍历所有的拓扑路径,然后取其最小学期数的路径作为答案,这是一种做法。

正解是使用状压dp,将学过的课程按照其编号-1置二进制串S的对应位为1,这样就可以用S来表示学过的课程集合。

定义f[i]表示状态为i需要学习的最少学期数,need[i]表示状态为i需要学习的前置课程集合。

首先考虑need的状态转移,我们枚举状态通常是从小到大枚举,所以推理出一个新的状态对应的need值可以用先前已经得到的小的状态的need值。我们可以取状态i的任意一个子集sub(由于sub是i的子集,所以值必然已经被计算出了)。显然状态i的前置课程就是子集sub的前置课程和去掉子集sub剩下的部分的前置课程的并集。所以有need[i] = need[sub] | need[i ^ sub],其中i ^ sub表示状态i去掉子集sub后剩下的那部分。为了方便,我们每次都取sublowbit(i)

最后考虑f的状态转移,对于状态i,可以得到对应需要学习的集合i ^ need[i]。如果该集合的课程数小于k,那么可以一次性全部学习掉,否则,枚举该集合的子集subf[i] = min(f[i ^ sub] + 1)。即,学哪个子集,可以让答案最小,就选哪个。

use std::cmp::min;
impl Solution {
    pub fn min_number_of_semesters(n: i32, relations: Vec<Vec<i32>>, k: i32) -> i32 
    {
        let mut f = vec![i32::MAX; 1 << n];
        let mut need = vec![0; 1 << n];

        for relation in relations {
            let (x, y) = (relation[0], relation[1]);
            need[1 << (y - 1)] |= 1 << (x - 1);
        }

        f[0] = 0;
        for i in 1i32..1 << n {
            let sub = (i & -i) as usize;
            need[i as usize] = need[i as usize ^ sub] | need[sub];
            if (need[i as usize] | i) != i { continue; }
            
            let valid = (i ^ need[i as usize]) as usize;
            if valid.count_ones() <= k as u32 { f[i as usize] = min(f[i as usize], f[i as usize ^ valid] + 1); }
            else {
                let mut sub = valid;
                while sub > 0 {
                    if sub.count_ones() <= k as u32 { f[i as usize] = min(f[i as usize], f[i as usize ^ sub] + 1); }
                    sub = (sub - 1) & valid;
                }
            }
        }

        f[(1 << n) - 1]
    }
}
posted @ 2023-06-16 15:30  烤肉kr  阅读(22)  评论(0)    收藏  举报