1494.并行课程II
题目描述:
给出 $n$ 个课程,编号 $1$ 到 $n$ , $relations[i] = [x_i,y_i]$ 表示一个先修课关系,课程 $x_i$ 必须在课程 $y_i$ 之前上完。同时还给出一个整数 $k$ 。
在一个学期中,最多可以同时上 $k$ 门课,前提是这些课的先修课在之前的学期已经上过。
请返回上完所有课最少需要多少个学期。保证存在一种方式上完所有的课程。
数据范围:
$1\le n \le 15, 1\le k \le n$
题解:
二进制子集枚举:
1 | // 降序遍历 x 的非空子集 |
最后的子集是空集,如果需要特殊处理需要跳出循环。
状压dp:
使用二进制来表示需要上哪些课,使用 $need[i]$ 表示课程 $i$ 的前置课程, $dp[i]$ 表示学完课程集合 $i$ 所需要的最少学期数。那么可以得到状态转移方程
也就是枚举集合 $[0, 1 << n)$ ,然后对于状态 $i$ 需要找到任意一个子集 $sub$ (因为比 $i$ 小的, $i$ 的子集都已经被算出来了,因此只需要找任意一个子集就行)。可以找 sub1 = i & (i - 1), sub2 = i ^ sub1
,并且刚好 sub2 = i & (-i)
,就是树状数组里面的原酸,用于找到最低位的 $1$ 。
当前学期学习 i ^ need[i]
,need[i]
必须在之前的学期完成,否则不能学习 $i$ 。因此判断是否合法,i | need[i] == i
时才合法。当前学期学习 valid = i ^ need[i]
,如果 $valid$ 中 $1$ 的个数小于等于 $k$ ,那么本学期可以直接学完,更新 dp[i] = min(dp[i], dp[i ^ valid] + 1)
。因为当前学期学习 $valid$ ,那么之前的学期学习的是 i ^ valid
。如果 $valid$ 中 $1$ 的个数大于 $k$ ,那么需要枚举 $valid$ 的子集 $sub$ ,对于 $1$ 的个数小于等于 $k$ 的子集,更新 dp[i] = min(dp[i], dp[i ^ sub] + 1)
。最后返回 $dp[(1 << n) - 1]$ 。
代码:
1 | auto optimize_cpp_stdio = []() |