{ }codelper
拓扑排序深度优先搜索P0326

只重做过期的文件

有 n 个文件,编号从 1 到 n。
第 i 个文件有最后修改时间 t_i,以及制作它时要用的材料文件列表。
材料列表为空的文件是人直接写的原件,不会被重做。
沿着材料走下去不会回到同一个文件。
为了让目标文件 g 变成最新,要“检查”g。
检查文件 x 的意思如下。
如果 x 已经检查过,就什么也不做。
否则先按列表中写的顺序逐个检查 x 的材料。
材料全部检查完之后,如果材料中至少有一个是这次重做过的,或者至少有一个的修改时间大于 t_x,就重做 x。
修改时间与 t_x 相同的材料不算比 x 新。
在检查 g 的过程中一次也没被检查到的文件,即使过期也不重做。
在样例中,检查 1 号时先检查材料 3 号。
3 号检查完材料 4 号和 5 号后,因为 5 号(70)比自己(60)新,所以重做。
接着检查 2 号,它的材料 4 号(30)比 2 号(40)旧,所以保持不变。
1 号(80)比所有材料修改得都晚,但 3 号刚刚重做过,所以 1 号也重做。
6 号比材料 5 号旧,但制作 1 号用不到它,所以保持不变。
请按重做的顺序求出被重做的文件。

输入
第一行给出 n 和 g,用空格分隔。
第二行给出 t_1 到 t_n,用一个空格分隔。
接下来 n 行中的第 i 行给出 i 号文件的材料数 k 和 k 个材料编号,用一个空格分隔(没有材料时只有 0)。
同一个文件的材料列表中不会出现两次相同的编号。
1 ≤ n ≤ 5,000,1 ≤ g ≤ n,1 ≤ t_i ≤ 1,000,000,000,所有文件的材料数之和不超过 10,000。
从 g 沿着材料往下走的任何一条路上,包括 g 在内文件不超过 1,000 个
输出
第一行输出重做的文件数。
第二行按重做的顺序输出这些编号,用一个空格分隔。
如果没有文件被重做,第一行只输出 0,不输出第二行
样例 1 · stdin
6 1
80 40 60 30 70 10
2 3 2
1 4
2 4 5
0
0
1 5
stdout
2
3 1
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

把材料全部检查完之后才记下自己,这个顺序就是深度优先搜索“离开时记录”的顺序(后序),它本身就是拓扑排序:任何文件都不会排在自己的材料前面。
数入边再用队列取出也是拓扑排序,但这里只做目标需要的文件,而且要按材料写的顺序做,所以适合用递归。
递归深度各语言不同。
Python 默认上限是 1,000,材料连着 1,000 层就会以 RecursionError 停下,要用 sys.setrecursionlimit(10000) 之类调高。
JS、TS、Java、C#、C、C++ 和 Craykon 在这个深度可以直接运行。
在函数里修改外面的标记列表的写法也不同。
Python 的内层函数修改外层列表的元素不需要 nonlocal;Java 和 C# 用 static 字段,C 用全局数组,JS、TS、C++ 直接用外面的变量。
Craykon 也在函数里像 looked[x] = 真; 这样直接修改外面的列表。
陷阱一:时间相同不算更新,是 > 而不是 >=。
陷阱二:即使材料上写的时间更旧,只要这次重做了那个材料,自己也要重做(样例的 1 号)。
所以“检查过”和“重做过”要分开标记。
陷阱三:不标记检查过的文件,两个文件共用的材料会被做两次。
陷阱四:从 1 号到 n 号全部检查,会把目标不需要的过期文件(样例的 6 号)也做了。
陷阱五:把材料按编号排序后再检查,或者把选出的文件按编号输出,顺序都会变(样例的答案是 3 1,不是 1 3)。

Pythonmain.py · 37 行
import sys sys.setrecursionlimit(10000)  # 재료를 따라 1,000단계까지 내려간다(기본 한도는 1,000) def main():    data = sys.stdin.read().split()    n, goal = int(data[0]), int(data[1])    t = [0] + [int(x) for x in data[2:2 + n]]    deps = [[]]    pos = 2 + n    for _ in range(n):        k = int(data[pos])        deps.append([int(x) for x in data[pos + 1:pos + 1 + k]])        pos += 1 + k     looked = [False] * (n + 1)   # 이미 살핀 파일    rebuilt = [False] * (n + 1)  # 이번에 다시 만든 파일    order = []                   # 다시 만든 차례     def look(x):        looked[x] = True        stale = False        for a in deps[x]:        # 재료를 적힌 차례대로 먼저 살핀다            if not looked[a]:                look(a)            if rebuilt[a] or t[a] > t[x]:                stale = True        if stale:                # 재료를 모두 살핀 뒤에야 자기를 만든다            rebuilt[x] = True            order.append(x)     look(goal)    print(len(order))    if order:        print(" ".join(map(str, order))) main()
JavaScriptmain.js · 34 行
const data = require("fs").readFileSync(0, "utf8").split(/\s+/).filter(Boolean).map(Number);const n = data[0];const goal = data[1];const t = [0, ...data.slice(2, 2 + n)];const deps = [[]];let pos = 2 + n;for (let i = 0; i < n; i++) {  const k = data[pos];  deps.push(data.slice(pos + 1, pos + 1 + k));  pos += 1 + k;} const looked = new Array(n + 1).fill(false); // 이미 살핀 파일const rebuilt = new Array(n + 1).fill(false); // 이번에 다시 만든 파일const order = []; // 다시 만든 차례 function look(x) {  looked[x] = true;  let stale = false;  for (const a of deps[x]) {    // 재료를 적힌 차례대로 먼저 살핀다    if (!looked[a]) look(a);    if (rebuilt[a] || t[a] > t[x]) stale = true;  }  if (stale) {    // 재료를 모두 살핀 뒤에야 자기를 만든다    rebuilt[x] = true;    order.push(x);  }} look(goal);console.log(order.length);if (order.length > 0) console.log(order.join(" "));
TypeScriptmain.ts · 35 行
import { readFileSync } from "node:fs";const data: number[] = readFileSync(0, "utf8").split(/\s+/).filter(Boolean).map(Number);const n: number = data[0];const goal: number = data[1];const t: number[] = [0, ...data.slice(2, 2 + n)];const deps: number[][] = [[]];let pos = 2 + n;for (let i = 0; i < n; i++) {  const k = data[pos];  deps.push(data.slice(pos + 1, pos + 1 + k));  pos += 1 + k;} const looked: boolean[] = new Array<boolean>(n + 1).fill(false); // 이미 살핀 파일const rebuilt: boolean[] = new Array<boolean>(n + 1).fill(false); // 이번에 다시 만든 파일const order: number[] = []; // 다시 만든 차례 function look(x: number): void {  looked[x] = true;  let stale = false;  for (const a of deps[x]) {    // 재료를 적힌 차례대로 먼저 살핀다    if (!looked[a]) look(a);    if (rebuilt[a] || t[a] > t[x]) stale = true;  }  if (stale) {    // 재료를 모두 살핀 뒤에야 자기를 만든다    rebuilt[x] = true;    order.push(x);  }} look(goal);console.log(order.length);if (order.length > 0) console.log(order.join(" "));
JavaMain.java · 58 行
import java.io.BufferedReader;import java.io.InputStreamReader;import java.io.StreamTokenizer;import java.util.ArrayList;import java.util.List; public class Main {    static int[] t;    static int[][] deps;    static boolean[] looked;   // 이미 살핀 파일    static boolean[] rebuilt;  // 이번에 다시 만든 파일    static List<Integer> order = new ArrayList<>(); // 다시 만든 차례     static void look(int x) {        looked[x] = true;        boolean stale = false;        for (int a : deps[x]) {          // 재료를 적힌 차례대로 먼저 살핀다            if (!looked[a]) look(a);            if (rebuilt[a] || t[a] > t[x]) stale = true;        }        if (stale) {                     // 재료를 모두 살핀 뒤에야 자기를 만든다            rebuilt[x] = true;            order.add(x);        }    }     public static void main(String[] args) throws Exception {        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));        in.nextToken();        int n = (int) in.nval;        in.nextToken();        int goal = (int) in.nval;        t = new int[n + 1];        for (int i = 1; i <= n; i++) {            in.nextToken();            t[i] = (int) in.nval;        }        deps = new int[n + 1][];        for (int i = 1; i <= n; i++) {            in.nextToken();            int k = (int) in.nval;            deps[i] = new int[k];            for (int j = 0; j < k; j++) {                in.nextToken();                deps[i][j] = (int) in.nval;            }        }        looked = new boolean[n + 1];        rebuilt = new boolean[n + 1];        look(goal);         StringBuilder out = new StringBuilder();        out.append(order.size()).append('\n');        for (int i = 0; i < order.size(); i++) out.append(i > 0 ? " " : "").append(order.get(i));        if (!order.isEmpty()) out.append('\n');        System.out.print(out);    }}
C#Main.cs · 50 行
using System;using System.Collections.Generic; class Program{    static int[] t;    static int[][] deps;    static bool[] looked;   // 이미 살핀 파일    static bool[] rebuilt;  // 이번에 다시 만든 파일    static List<int> order = new List<int>(); // 다시 만든 차례     static void Look(int x)    {        looked[x] = true;        bool stale = false;        foreach (int a in deps[x])       // 재료를 적힌 차례대로 먼저 살핀다        {            if (!looked[a]) Look(a);            if (rebuilt[a] || t[a] > t[x]) stale = true;        }        if (stale)                       // 재료를 모두 살핀 뒤에야 자기를 만든다        {            rebuilt[x] = true;            order.Add(x);        }    }     static void Main()    {        string[] data = Console.In.ReadToEnd().Split((char[])null, StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(data[0]), goal = int.Parse(data[1]);        t = new int[n + 1];        for (int i = 1; i <= n; i++) t[i] = int.Parse(data[1 + i]);        deps = new int[n + 1][];        int pos = 2 + n;        for (int i = 1; i <= n; i++)        {            int k = int.Parse(data[pos]);            deps[i] = new int[k];            for (int j = 0; j < k; j++) deps[i][j] = int.Parse(data[pos + 1 + j]);            pos += 1 + k;        }        looked = new bool[n + 1];        rebuilt = new bool[n + 1];        Look(goal);         Console.WriteLine(order.Count);        if (order.Count > 0) Console.WriteLine(string.Join(" ", order));    }}
C++main.cpp · 44 行
#include <iostream>#include <vector> std::vector<int> t;std::vector<std::vector<int>> deps;std::vector<bool> looked;   // 이미 살핀 파일std::vector<bool> rebuilt;  // 이번에 다시 만든 파일std::vector<int> order;     // 다시 만든 차례 void look(int x) {    looked[x] = true;    bool stale = false;    for (int a : deps[x]) {              // 재료를 적힌 차례대로 먼저 살핀다        if (!looked[a]) look(a);        if (rebuilt[a] || t[a] > t[x]) stale = true;    }    if (stale) {                         // 재료를 모두 살핀 뒤에야 자기를 만든다        rebuilt[x] = true;        order.push_back(x);    }} int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int n, goal;    std::cin >> n >> goal;    t.assign(n + 1, 0);    for (int i = 1; i <= n; i++) std::cin >> t[i];    deps.assign(n + 1, {});    for (int i = 1; i <= n; i++) {        int k;        std::cin >> k;        deps[i].resize(k);        for (int &a : deps[i]) std::cin >> a;    }    looked.assign(n + 1, false);    rebuilt.assign(n + 1, false);    look(goal);     std::cout << order.size() << '\n';    for (std::size_t i = 0; i < order.size(); i++) std::cout << (i ? " " : "") << order[i];    if (!order.empty()) std::cout << '\n';}
Cmain.c · 44 行
#include <stdio.h> #define MAXN 5001#define MAXM 10000 int t[MAXN];int start[MAXN + 1];   /* i번 파일의 재료는 dep[start[i]] … dep[start[i + 1] - 1] */int dep[MAXM + 1];int looked[MAXN];      /* 이미 살핀 파일 */int rebuilt[MAXN];     /* 이번에 다시 만든 파일 */int order[MAXN], made = 0; void look(int x) {    looked[x] = 1;    int stale = 0;    for (int j = start[x]; j < start[x + 1]; j++) {   /* 재료를 적힌 차례대로 먼저 살핀다 */        int a = dep[j];        if (!looked[a]) look(a);        if (rebuilt[a] || t[a] > t[x]) stale = 1;    }    if (stale) {                                      /* 재료를 모두 살핀 뒤에야 자기를 만든다 */        rebuilt[x] = 1;        order[made++] = x;    }} int main(void) {    int n, goal;    scanf("%d %d", &n, &goal);    for (int i = 1; i <= n; i++) scanf("%d", &t[i]);    int m = 0;    for (int i = 1; i <= n; i++) {        int k;        scanf("%d", &k);        start[i] = m;        for (int j = 0; j < k; j++) scanf("%d", &dep[m++]);    }    start[n + 1] = m;     look(goal);    printf("%d\n", made);    for (int i = 0; i < made; i++) printf(i + 1 < made ? "%d " : "%d\n", order[i]);    return 0;}
Craykonmain.cray · 48 行
첫줄 = 控制台.读数字();n = 첫줄[0];goal = 첫줄[1];t = 控制台.读数字(); // t[i - 1]: i번 파일을 고친 시각deps = [];循环 (i = 0; i < n; i++){    줄 = 控制台.读数字();    재료 = [];    循环 (j = 1; j <= 줄[0]; j++)    {        재료.添加(줄[j] - 1);    }    deps.添加(재료);} looked = 新列表(n, 假); // 이미 살핀 파일rebuilt = 新列表(n, 假); // 이번에 다시 만든 파일order = []; // 다시 만든 차례 살피기(x){    looked[x] = 真;    stale = 假;    遍历 (a 在 deps[x])    {        如果 (!looked[a])        {            살피기(a);        }        如果 (rebuilt[a] || t[a] > t[x])        {            stale = 真;        }    }    如果 (stale)    {        rebuilt[x] = 真;        order.添加(x + 1);    }} 살피기(goal - 1);控制台.输出(order.数量);如果 (order.数量 > 0){    控制台.输出(order.连接(" "));}