2023.6.16 并行课程 II
想当难的一道状压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后剩下的那部分。为了方便,我们每次都取sub
为lowbit(i)
。
最后考虑f
的状态转移,对于状态i
,可以得到对应需要学习的集合i ^ need[i]
。如果该集合的课程数小于k,那么可以一次性全部学习掉,否则,枚举该集合的子集sub
,f[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]
}
}