在线评测链接:P1229 (opens new window)
# 题目内容
塔子哥是一名软件工程师,他正在为一家游戏公司开发一个新的3D引擎。这个引擎由多个模块组成,每个模块负责不同的功能,比如渲染、物理、音效、网络等。
为了提高性能和稳定性,塔子哥需要在游戏启动时初始化这些模块,但是有一个问题:不同的模块之间存在依赖关系,比如渲染模块依赖于物理模块,音效模块依赖于网络模块等。如果塔子哥不按照正确的顺序初始化这些模块,就会导致错误或崩溃。
为了解决以上问题,塔子哥决定开发一个代码分析工具,用来分析代码模块之间的依赖关系,用来确定模块的初始化顺序、是否有循环依赖等问题。
塔子哥的工具可以一次初始化一个或多个模块,只要它们之间没有依赖关系。他把这个过程称为引擎模块初始化。
现在,塔子哥已经得到了一组模块间的依赖关系,塔子哥需要计算需要引擎模块初始化的次数。
# 输入描述
输入第一行为一个整数 ,表示模块总数。
接下来的 行依次表示模块 到 的依赖关系。
输入每行的第一个数为一个整数 ,表示依赖的模块数量(不会超过 ),之后的数字表示当前模块依赖的模块ID序列,该序列不会重复出现相同的数字,并且模块ID的取值一定在 之内。
每一行里面的数字按一个空格分隔。
# 输出描述
输出”批量初始化次数”。
若有循环依赖无法完成初始化,则输出 。
# 样例
# 样例一
输入
5
3 2 3 4
1 5
1 5
1 5
0
输出
3
样例解释
共 个模块。
模块 依赖模块 、 、 ;
模块 依赖模块 ;
模块 依款模块 ;
模块 依赖模块 ;
模块 没有依赖任何模块。
批量初始化顺序为 ,共需”批量初始化” 次。
# 样例二
输入
3
1 2
1 3
1 1
输出
-1
样例解释
存在循环依赖,无法完成初始化,返回 。
# 思路:拓扑排序
# 解法1:DAG上的最长路
根据题目意思以及样例,我们可以发现实际上就是给我们一张有向无环图。问DAG上的最长路径。我们可以使用拓扑排序+动态规划求解。
要求最长路径,我们定义 为从任意起点到点的最长路径长度,那么转移为:
其中是点的前驱节点。在拓扑排序的过程中进行刷表法即可。最终答案为
# 小知识
动态规划转移的编码实现时有两种常见方法,
刷表法:对每个点,用该点的值更新后继节点的值
填表法:对每个点,用所有前驱节点的值来更新该该的值
一般来说我们的转移方程都是以填表法的形式描述,但是在实现的过程中并不一定总是要采用填表法的形式。例如本题,由于拓扑排序访问节点的特点,我们自然考虑刷表法。当然本题也存在填表法的方法,即直接记忆化搜索求解DAG上的最长路。
关于这两种方法的更多讨论,见《算法竞赛入门经典》中动态规划一章。
# 解法2:拓扑排序
实际上解法一的数组可以不要。我们直接拓扑排序,然后记录运行的轮数即可。这样就变成了拓扑排序裸题
# 类似题目推荐
这是一道比较基础的拓扑排序题。需要更多的习题来加强练习!
# LeetCode
207. 课程表 (opens new window) - 级别1
Luogu-P1347 排序 (opens new window) 级别2
放这道题是由于力扣上的这题:序列重建 收费了。。。 来个免费的版本做做
1857. 有向图中最大颜色值 (opens new window)
拓扑排序 + 动态规划
*1494. 并行课程 II (opens new window)
很容易误以为是拓扑排序+贪心,但是实际上是一道竞赛难度的状压dp + 枚举子集的题。 可以不做
# CodeFun2000
P1145 2023.4.2-网易有道翻译-研发岗-第四题-机器采草莓最优决策 (opens new window)
P1029 华为秋招-2022.11.3-指令排序 (opens new window)
# 代码
# C++
解法1
#include<bits/stdc++.h>
using namespace std;
/*
e:邻接表
d:入度
dp:从任意源点到该点的最长路径
*/
vector<int> e[10005];
int d[10005] , dp[10005];
int main(){
// 读入 + 建图
int n;
cin >> n;
for (int i = 1 ; i <= n; i ++){
int m;
cin >> m;
for (int j = 1 ; j <= m ; j++){
int x;
cin >> x;
e[x].push_back(i);
d[i]++;
}
}
// 拓扑排序
queue<int> q;
for (int i = 1 ; i <= n ; i++)
if (d[i] == 0)
q.push(i) , dp[i] = 1;
while (q.size()){
int u = q.front();
q.pop();
for (auto v : e[u]){
// 转移
dp[v] = max(dp[v] , dp[u] + 1);
d[v]--;
if (d[v] == 0){
q.push(v);
}
}
}
// 输出答案
int mx = 0;
for (int i = 1 ; i <= n ; i++){
if (dp[i] == 0){
cout << -1 << endl;
return 0;
}
mx = max(mx , dp[i]);
}
cout << mx << endl;
return 0;
}
# python
解法2
from collections import defaultdict,deque
n = int(input())
edges = defaultdict(list)
in_ = [0] *(n+1)
for i in range(1,n+1):
temp = list(map(int,input().split()))
for j in range(1,len(temp)):
edges[temp[j]].append(i)
in_[i] += 1
queue = deque()
for i in range(1, n+1):
if in_[i] ==0:
queue.append(i)
if not queue:
print(-1, end=' ')
else:
res = 0
while queue:
res += 1
n = len(queue)
for i in range(n):
from_ = queue[0]
queue.popleft()
for to_ in edges[from_]:
in_[to_] -= 1
if in_[to_] == 0:
queue.append(to_)
print(res)
# Java
import java.util.*;
public class Main {
public void solution(){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] inDegree = new int[n + 1];
inDegree[0] = -1;
ArrayList<Integer>[] outDegree = new ArrayList[n + 1];
for(int i = 1; i <= n; ++i){
int m = scanner.nextInt();
inDegree[i] += m;
for(int j = 0; j < m; ++j){
int from = scanner.nextInt();
if(outDegree[from] == null)
outDegree[from] = new ArrayList<>();
outDegree[from].add(i);
}
}
scanner.close();
int res = 0;
boolean check;
Queue<Integer> queue = new LinkedList<>();
do {
check = false;
for(int i = 1; i < inDegree.length; ++i){
if(inDegree[i] == 0){
inDegree[i] = -1;
check = true;
queue.offer(i);
}
}
if(check)
res += 1;
while(!queue.isEmpty()){
int from = queue.poll();
if(outDegree[from] == null)
continue;
for(int i = 0; i < outDegree[from].size(); ++i)
--inDegree[outDegree[from].get(i)];
}
}while(check);
for(int i = 1; i < inDegree.length; ++i){
if(inDegree[i] != -1){
System.out.println(-1);
return;
}
}
System.out.println(res);
}
public static void main(String[] args) {
Main m = new Main();
m.solution();
}
}
# Go
package main
import (
"bufio"
"fmt"
"os"
)
const N = 1e3 + 10
var n, m int
func main() {
inDegrees := make([]int, N)
outDegrees := make([][]int, N)
for i := range outDegrees {
outDegrees[i] = make([]int, 0)
}
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
fmt.Fscan(in, &n)
for i := 1; i <= n; i++ {
fmt.Fscan(in, &m)
var id int
inDegrees[i] = m
for j := 0; j < m; j++ {
fmt.Fscan(in, &id)
outDegrees[id] = append(outDegrees[id], i)
}
}
queue := make([]int, 0)
for i := 1; i <= n; i++ {
if inDegrees[i] == 0 {
queue = append(queue, i)
}
}
if len(queue) == 0 {
fmt.Fprintln(out, -1)
return
}
res, count := 0, 0
for len(queue) != 0 {
size := len(queue)
count += size
for i := 0; i < size; i++ {
front := queue[0]
queue = queue[1:]
for _, o := range outDegrees[front] {
inDegrees[o]--
if inDegrees[o] == 0 {
queue = append(queue, o)
}
}
}
res++
}
if count != n {
//循环依赖
fmt.Fprintln(out, -1)
return
}
fmt.Fprintln(out, res)
}
# Js
/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const lines = [];
let n;
rl.on("line", (line) => {
lines.push(line);
if (lines.length == 1) {
// 模块总数N
n = lines[0] - 0;
}
if (n && lines.length == n + 1) {
// 记录每个服务的入度值
const inDegree = new Array(n + 1).fill(0);
// 记录每个服务的所有后继服务
const next = {};
// 模块从1~N,这里将 i 作为模块标识
for (let i = 1; i <= n; i++) {
if (!next[i]) next[i] = [];
const line = lines[i].split(" ").map(Number);
// 统计入度值
inDegree[i] = line[0];
// 统计后继服务
for (let j = 1; j < line.length; j++) {
const pre = line[j];
next[pre] ? next[pre].push(i) : (next[pre] = [i]);
}
}
console.log(getResult(n, next, inDegree));
lines.length = 0;
}
});
/**
*
* @param {*} n 模块总数N
* @param {*} next 记录每个服务的所有后继服务
* @param {*} inDegree 记录每个服务的入度值
* @returns 输出"批量初始化次数”。若有循环依赖无法完成初始化,则输出-1。
*/
function getResult(n, next, inDegree) {
// queue记录入度值为0的点
let queue = [];
for (let i = 1; i <= n; i++) {
if (inDegree[i] == 0) queue.push(i);
}
// ans记录"批量初始化次数”
let ans = 0;
// count记录已成功部署的模块,如果存在循环依赖,则最终count < n
let count = 0;
while (queue.length) {
const newQueue = [];
// 遍历入度值为0的模块
for (let id of queue) {
// 每遍历一个,就说明有一个模块部署成功
count++;
// 一个模块部署成功,则其后继模块的入度值需要减1
for (let nextId of next[id]) {
// 如果后继模块的入度值变为了0,则说明也可以部署了
if (--inDegree[nextId] == 0) newQueue.push(nextId);
}
}
// 这里使用newQueue的原因是,保证每次都能剥离一层入度为0的模块,这样会方便ans统计
queue = newQueue;
ans++;
}
// 如果存在循环依赖,则最后成功部署的模块数count < n
return count == n ? ans : -1;
}
// by sunde
← 第2题-塔子哥出城 第2题-分配资源ID →