2023年4月26日-暑期实习-第一题(100分)-引擎模块初始化

在线评测链接

题目内容

塔子哥是一名软件工程师,他正在为一家游戏公司开发一个新的3D引擎。这个引擎由多个模块组成,每个模块负责不同的功能,比如渲染、物理、音效、网络等。

为了提高性能和稳定性,塔子哥需要在游戏启动时初始化这些模块,但是有一个问题:不同的模块之间存在依赖关系,比如渲染模块依赖于物理模块,音效模块依赖于网络模块等。如果塔子哥不按照正确的顺序初始化这些模块,就会导致错误或崩溃。

为了解决以上问题,塔子哥决定开发一个代码分析工具,用来分析代码模块之间的依赖关系,用来确定模块的初始化顺序、是否有循环依赖等问题。

塔子哥的工具可以一次初始化一个或多个模块,只要它们之间没有依赖关系。他把这个过程称为引擎模块初始化。

现在,塔子哥已经得到了一组模块间的依赖关系,塔子哥需要计算需要引擎模块初始化的次数。

输入描述

输入第一行为一个整数 n n n ,表示模块总数。

接下来的 n n n 行依次表示模块 1 1 1 n n n 的依赖关系。

输入每行的第一个数为一个整数 m m m ,表示依赖的模块数量(不会超过 n n n ),之后的数字表示当前模块依赖的模块ID序列,该序列不会重复出现相同的数字,并且模块ID的取值一定在 [ 1 , n ] [1,n] [1,n] 之内。

每一行里面的数字按一个空格分隔。

1 ≤ m ≤ n ≤ 1000 1\le m\le n \le 1000 1mn1000

输出描述

输出”批量初始化次数”。

若有循环依赖无法完成初始化,则输出 − 1 -1 1

样例

样例一

输入

5
3 2 3 4
1 5
1 5
1 5
0

输出

3

样例解释

5 5 5 个模块。

模块 1 1 1 依赖模块 2 2 2 3 3 3 4 4 4

模块 2 2 2 依赖模块 5 5 5

模块 3 3 3 依款模块 5 5 5

模块 4 4 4 依赖模块 5 5 5

模块 5 5 5 没有依赖任何模块。

批量初始化顺序为 { 5 } → { 2 , 3 , 4 } → { 1 } \{5\}\rightarrow \{2,3,4\}\rightarrow \{1\} {5}{2,3,4}{1} ,共需”批量初始化” 3 3 3 次。

样例二

输入

3
1 2
1 3
1 1

输出

-1

样例解释

存在循环依赖,无法完成初始化,返回 − 1 -1 1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

思路:拓扑排序

解法1:DAG上的最长路

根据题目意思以及样例,我们可以发现实际上就是给我们一张有向无环图。问DAG上的最长路径。我们可以使用拓扑排序+动态规划求解。

要求最长路径,我们定义 d p [ i ] dp[i] dp[i] 为从任意起点到点 i i i的最长路径长度,那么转移为:
d p i = m a x ( d p j ) + 1 , j ∈ p r e ( i ) dp_i = max(dp_j) + 1 , j \in pre(i) dpi=max(dpj)+1,jpre(i)
其中 p r e ( i ) pre(i) pre(i) i i i点的前驱节点。在拓扑排序的过程中进行刷表法即可。最终答案为 m a x ( d p ) max(dp) max(dp)

小知识

动态规划转移的编码实现时有两种常见方法,

刷表法:对每个点,用该点的值更新后继节点的值

填表法:对每个点,用所有前驱节点的值来更新该该的值

一般来说我们的转移方程都是以填表法的形式描述,但是在实现的过程中并不一定总是要采用填表法的形式。例如本题,由于拓扑排序访问节点的特点,我们自然考虑刷表法。当然本题也存在填表法的方法,即直接记忆化搜索求解DAG上的最长路。

关于这两种方法的更多讨论,见《算法竞赛入门经典》中动态规划一章。

解法2:拓扑排序

实际上解法一的 d p dp dp数组可以不要。我们直接拓扑排序,然后记录运行的轮数即可。这样就变成了拓扑排序裸题

类似题目推荐

这是一道比较基础的拓扑排序题。需要更多的习题来加强练习!

LeetCode

207. 课程表 - 级别1

Luogu-P1347 排序 级别2

放这道题是由于力扣上的这题:序列重建 收费了。。。 来个免费的版本做做

1857. 有向图中最大颜色值

拓扑排序 + 动态规划

*1494. 并行课程 II

很容易误以为是拓扑排序+贪心,但是实际上是一道竞赛难度的状压dp + 枚举子集的题。 可以不做

CodeFun2000

P1145 2023.4.2-网易有道翻译-研发岗-第四题-机器采草莓最优决策

P1029 华为秋招-2022.11.3-指令排序

代码

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

  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值