{ }codelper
深度优先搜索递归64 位整数P0337

从下往上跳闸的断路器

一栋楼里有 n 个断路器,编号从 1 到 n。
断路器之间由 n − 1 根电线相连,一根电线“a b”连接 a 号断路器和 b 号断路器。
沿着电线走,从任何一个断路器都能到达其他所有断路器。
1 号断路器是与发电厂相连的最上面的断路器。
用电线相连的两个断路器中,离 1 号断路器更近的是“上级断路器”,另一个是“下级断路器”。
输入中 a 和 b 哪个是上级并不固定。
i 号断路器上接着用电量为 uᵢ 的设备,断路器的容量是 cᵢ。
流过 i 号断路器的电量 Lᵢ 等于 uᵢ 加上与 i 号断路器用电线直接相连的下级断路器中没有跳闸的那些的 L 之和。
如果 Lᵢ 大于 cᵢ,i 号断路器就跳闸。
如果 Lᵢ 等于 cᵢ,则不跳闸。
跳闸的断路器的 L 不加到上级断路器的 L 里。
所以是否跳闸要从最下面的断路器开始依次确定。
一个断路器要有电,它自己以及从它到 1 号断路器的路上的断路器都不能跳闸。
管理员从 1 号断路器开始,按下面的方法检查断路器。
如果当前断路器已跳闸,就记下它的编号,并且不再往它下面走。
如果没有跳闸,就从编号小的开始逐个检查它的下级断路器。
选定一个后,先用同样的方法把它下面全部检查完,再去下一个下级断路器。
第一行输出有电的断路器的数量。
第二行输出 L₁。
即使 1 号断路器跳闸,L₁ 也是按上面的规则求出的值。
第三行按记下的顺序输出管理员记下的编号,用一个空格隔开。
如果一个编号也没记,输出 -。

输入
第一行给出 n,第二行给出 u₁ … uₙ,第三行给出 c₁ … cₙ,接下来 n − 1 行每行给出 a 和 b。
一行中的数用一个空格隔开。
1 ≤ n ≤ 9,000,0 ≤ uᵢ ≤ 1,000,000,000,0 ≤ cᵢ ≤ 10,000,000,000,000(10 万亿),1 ≤ a, b ≤ n,a ≠ b。
从 1 号断路器到任何断路器最多经过 1,200 根电线。
全部是整数
输出
第一行是有电的断路器的数量,第二行是 L₁(只写数字,不加逗号),第三行是管理员记下的编号,或者 -
样例 1 · stdin
7
0 10 5 30 20 40 5
100 50 44 25 20 50 4
1 3
2 1
4 2
2 5
7 3
3 6
stdout
3
30
4 3
main.py
输入(stdin)

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

$ 就绪

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

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

要做两次深度优先搜索(DFS)。
一次先走到最下面,再在返回的路上把 L 加起来(因为下面的要先确定),另一次从上往下数哪里有电。
电线上没有写哪头是上级,所以两个方向都连上,只是不走“刚来的地方”。
样例中 7 号跳闸(5 > 4),所以 3 号的 L 是 5 + 40 = 45,大于 44,3 号也跳闸。
4 号也跳闸(30 > 25),所以 2 号的 L 是 10 + 20 = 30,1 号也是 30。
陷阱一:如果把跳闸断路器的电量也加到上级,2 号会变成 60 而跳闸,连 1 号也跳闸。
陷阱二:5 号是 20 = 20,撑得住。
写成 >= 的话 5 号也会跳闸。
陷阱三:管理员不会走到跳闸断路器的下面。
如果继续往下走,会连 7 号也记下,变成 4 3 7。
陷阱四:下级断路器按编号顺序。
按输入顺序看会得到 3 4。
JS、TS 的 sort() 不给比较函数时按字符串比较,10 会排在 9 前面,要写 sort((x, y) => x - y)。
Python list.sort()、Java Collections.sort、C# List.Sort()、C++ std::sort、C qsort、Craykon 列表.排序() 都按数的大小比较。
陷阱五:L 会大到 9 万亿。
Java、C# 的 int 不报错地变成错误的数,C、C++ 则是未定义行为,所以要用 long、long long。
Python 没有上限,JS、TS、Craykon 在 2^53 以内是精确的。
陷阱六:递归深度。
Python 的默认上限是 1,000,深度 1,200 的输入会因 RecursionError 停下,要用 sys.setrecursionlimit 调高,或者自己用栈。
其他语言在这个深度没有问题,但并不是没有上限(Craykon 是几千层)。
Craykon 的函数像 电量(v, 上) { … 返回 total; } 这样用名字和括号直接定义。

Pythonmain.py · 55 行
import sys sys.setrecursionlimit(5000)  # 기본 한도 1,000으로는 깊이 1,200까지 못 내려간다  def main():    data = sys.stdin.buffer.read().split()    n = int(data[0])    use = [0] + [int(x) for x in data[1:1 + n]]    cap = [0] + [int(x) for x in data[1 + n:1 + 2 * n]]    near = [[] for _ in range(n + 1)]  # 전선은 방향 없이 주어진다    pos = 1 + 2 * n    for _ in range(n - 1):        a, b = int(data[pos]), int(data[pos + 1])        pos += 2        near[a].append(b)        near[b].append(a)    for v in range(1, n + 1):        near[v].sort()  # 번호가 작은 아래쪽 차단기부터 본다     tripped = [False] * (n + 1)     def load(v, parent):        """v를 지나는 전기의 양을 돌려준다. 아래쪽부터 정해야 하므로 내려갔다 올라오면서 더한다."""        total = use[v]        for w in near[v]:            if w != parent:                below = load(w, v)                if not tripped[w]:  # 내려간 차단기 아래로는 전기가 흐르지 않는다                    total += below        tripped[v] = total > cap[v]        return total     lit = 0    fix = []     def walk(v, parent):        """1번에서 내려가며 전기가 오는 곳을 세고, 맨 먼저 올릴 차단기를 적는다."""        nonlocal lit        if tripped[v]:            fix.append(v)  # 이 아래는 어차피 전기가 안 오니 내려가지 않는다            return        lit += 1        for w in near[v]:            if w != parent:                walk(w, v)     root_load = load(1, 0)    walk(1, 0)    print(lit)    print(root_load)    print(" ".join(map(str, fix)) if fix else "-")  main()
JavaScriptmain.js · 54 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];const use = [0];const cap = [0];for (let v = 1; v <= n; v++) {  use.push(data[v]);  cap.push(data[n + v]);}// 전선은 방향 없이 주어진다const near = Array.from({ length: n + 1 }, () => []);for (let i = 0; i < n - 1; i++) {  const a = data[1 + 2 * n + 2 * i];  const b = data[2 + 2 * n + 2 * i];  near[a].push(b);  near[b].push(a);}// 번호가 작은 아래쪽 차단기부터 본다. sort()는 그냥 쓰면 문자열로 견주니 비교 함수를 준다for (const list of near) list.sort((x, y) => x - y); const tripped = new Array(n + 1).fill(false); // v를 지나는 전기의 양. 아래쪽부터 정해야 하므로 내려갔다 올라오면서 더한다function load(v, parent) {  let total = use[v]; // 9조까지 커져도 2^53 안이라 정확하다  for (const w of near[v]) {    if (w !== parent) {      const below = load(w, v);      if (!tripped[w]) total += below; // 내려간 차단기 아래로는 전기가 흐르지 않는다    }  }  tripped[v] = total > cap[v];  return total;} let lit = 0;const fix = []; // 1번에서 내려가며 전기가 오는 곳을 세고, 맨 먼저 올릴 차단기를 적는다function walk(v, parent) {  if (tripped[v]) {    fix.push(v); // 이 아래는 어차피 전기가 안 오니 내려가지 않는다    return;  }  lit++;  for (const w of near[v]) {    if (w !== parent) walk(w, v);  }} const rootLoad = load(1, 0);walk(1, 0);console.log(lit);console.log(rootLoad);console.log(fix.length > 0 ? fix.join(" ") : "-");
TypeScriptmain.ts · 56 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];const use: number[] = [0];const cap: number[] = [0];for (let v = 1; v <= n; v++) {  use.push(data[v]);  cap.push(data[n + v]);}// 전선은 방향 없이 주어진다const near: number[][] = Array.from({ length: n + 1 }, (): number[] => []);for (let i = 0; i < n - 1; i++) {  const a = data[1 + 2 * n + 2 * i];  const b = data[2 + 2 * n + 2 * i];  near[a].push(b);  near[b].push(a);}// 번호가 작은 아래쪽 차단기부터 본다. sort()는 그냥 쓰면 문자열로 견주니 비교 함수를 준다for (const list of near) list.sort((x, y) => x - y); const tripped: boolean[] = new Array<boolean>(n + 1).fill(false); // v를 지나는 전기의 양. 아래쪽부터 정해야 하므로 내려갔다 올라오면서 더한다function load(v: number, parent: number): number {  let total = use[v]; // 9조까지 커져도 2^53 안이라 정확하다  for (const w of near[v]) {    if (w !== parent) {      const below = load(w, v);      if (!tripped[w]) total += below; // 내려간 차단기 아래로는 전기가 흐르지 않는다    }  }  tripped[v] = total > cap[v];  return total;} let lit = 0;const fix: number[] = []; // 1번에서 내려가며 전기가 오는 곳을 세고, 맨 먼저 올릴 차단기를 적는다function walk(v: number, parent: number): void {  if (tripped[v]) {    fix.push(v); // 이 아래는 어차피 전기가 안 오니 내려가지 않는다    return;  }  lit++;  for (const w of near[v]) {    if (w !== parent) walk(w, v);  }} const rootLoad = load(1, 0);walk(1, 0);console.log(lit);console.log(rootLoad);console.log(fix.length > 0 ? fix.join(" ") : "-");
JavaMain.java · 62 行
import java.io.*;import java.util.*; public class Main {    static long[] use, cap;    static List<List<Integer>> near = new ArrayList<>();    static boolean[] tripped;    static int lit = 0;    static StringBuilder fix = new StringBuilder();     // v를 지나는 전기의 양. 아래쪽부터 정해야 하므로 내려갔다 올라오면서 더한다    static long load(int v, int parent) {        long total = use[v]; // 합이 9조까지 커지므로 int로는 모자란다        for (int w : near.get(v)) {            if (w != parent) {                long below = load(w, v);                if (!tripped[w]) total += below; // 내려간 차단기 아래로는 전기가 흐르지 않는다            }        }        tripped[v] = total > cap[v];        return total;    }     // 1번에서 내려가며 전기가 오는 곳을 세고, 맨 먼저 올릴 차단기를 적는다    static void walk(int v, int parent) {        if (tripped[v]) {            if (fix.length() > 0) fix.append(' ');            fix.append(v); // 이 아래는 어차피 전기가 안 오니 내려가지 않는다            return;        }        lit++;        for (int w : near.get(v)) {            if (w != parent) walk(w, v);        }    }     public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(br.readLine().trim());        use = new long[n + 1];        cap = new long[n + 1];        tripped = new boolean[n + 1];        StringTokenizer st = new StringTokenizer(br.readLine());        for (int v = 1; v <= n; v++) use[v] = Long.parseLong(st.nextToken());        st = new StringTokenizer(br.readLine());        for (int v = 1; v <= n; v++) cap[v] = Long.parseLong(st.nextToken()); // 용량은 10조까지라 long으로 읽는다        for (int v = 0; v <= n; v++) near.add(new ArrayList<>());        for (int i = 0; i < n - 1; i++) { // 전선은 방향 없이 주어진다            st = new StringTokenizer(br.readLine());            int a = Integer.parseInt(st.nextToken()), b = Integer.parseInt(st.nextToken());            near.get(a).add(b);            near.get(b).add(a);        }        for (List<Integer> list : near) Collections.sort(list); // 번호가 작은 아래쪽 차단기부터 본다         long rootLoad = load(1, 0);        walk(1, 0);        System.out.println(lit);        System.out.println(rootLoad);        System.out.println(fix.length() > 0 ? fix.toString() : "-");    }}
C#Main.cs · 71 行
using System;using System.Collections.Generic; class Program{    static long[] use = Array.Empty<long>(), cap = Array.Empty<long>();    static List<int>[] near = Array.Empty<List<int>>();    static bool[] tripped = Array.Empty<bool>();    static int lit = 0;    static readonly List<int> fix = new List<int>();     // v를 지나는 전기의 양. 아래쪽부터 정해야 하므로 내려갔다 올라오면서 더한다    static long Load(int v, int parent)    {        long total = use[v]; // 합이 9조까지 커지므로 int로는 모자란다        foreach (int w in near[v])        {            if (w != parent)            {                long below = Load(w, v);                if (!tripped[w]) total += below; // 내려간 차단기 아래로는 전기가 흐르지 않는다            }        }        tripped[v] = total > cap[v];        return total;    }     // 1번에서 내려가며 전기가 오는 곳을 세고, 맨 먼저 올릴 차단기를 적는다    static void Walk(int v, int parent)    {        if (tripped[v])        {            fix.Add(v); // 이 아래는 어차피 전기가 안 오니 내려가지 않는다            return;        }        lit++;        foreach (int w in near[v])        {            if (w != parent) Walk(w, v);        }    }     static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(data[0]);        use = new long[n + 1];        cap = new long[n + 1];        tripped = new bool[n + 1];        near = new List<int>[n + 1];        for (int v = 0; v <= n; v++) near[v] = new List<int>();        for (int v = 1; v <= n; v++)        {            use[v] = long.Parse(data[v]);            cap[v] = long.Parse(data[n + v]); // 용량은 10조까지라 long으로 읽는다        }        for (int i = 0; i < n - 1; i++) // 전선은 방향 없이 주어진다        {            int a = int.Parse(data[1 + 2 * n + 2 * i]), b = int.Parse(data[2 + 2 * n + 2 * i]);            near[a].Add(b);            near[b].Add(a);        }        foreach (var list in near) list.Sort(); // 번호가 작은 아래쪽 차단기부터 본다         long rootLoad = Load(1, 0);        Walk(1, 0);        Console.WriteLine(lit);        Console.WriteLine(rootLoad);        Console.WriteLine(fix.Count > 0 ? string.Join(" ", fix) : "-");    }}
C++main.cpp · 65 行
#include <algorithm>#include <iostream>#include <vector>using namespace std; vector<long long> use, cap;vector<vector<int>> near_;   // near_[v] = v와 전선으로 이어진 차단기들vector<bool> tripped;int lit = 0;vector<int> fix; // v를 지나는 전기의 양. 아래쪽부터 정해야 하므로 내려갔다 올라오면서 더한다long long load(int v, int parent) {    long long total = use[v]; // 합이 9조까지 커지므로 int로는 모자란다    for (int w : near_[v]) {        if (w != parent) {            long long below = load(w, v);            if (!tripped[w]) total += below; // 내려간 차단기 아래로는 전기가 흐르지 않는다        }    }    tripped[v] = total > cap[v];    return total;} // 1번에서 내려가며 전기가 오는 곳을 세고, 맨 먼저 올릴 차단기를 적는다void walk(int v, int parent) {    if (tripped[v]) {        fix.push_back(v); // 이 아래는 어차피 전기가 안 오니 내려가지 않는다        return;    }    lit++;    for (int w : near_[v]) {        if (w != parent) walk(w, v);    }} int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n;    cin >> n;    use.assign(n + 1, 0);    cap.assign(n + 1, 0);    for (int v = 1; v <= n; v++) cin >> use[v];    for (int v = 1; v <= n; v++) cin >> cap[v];    near_.assign(n + 1, {});    for (int i = 0; i < n - 1; i++) { // 전선은 방향 없이 주어진다        int a, b;        cin >> a >> b;        near_[a].push_back(b);        near_[b].push_back(a);    }    for (auto &list : near_) sort(list.begin(), list.end()); // 번호가 작은 아래쪽 차단기부터 본다     tripped.assign(n + 1, false);    long long rootLoad = load(1, 0);    walk(1, 0);    cout << lit << "\n" << rootLoad << "\n";    if (fix.empty()) {        cout << "-\n";    } else {        for (size_t i = 0; i < fix.size(); i++) cout << fix[i] << (i + 1 < fix.size() ? ' ' : '\n');    }    return 0;}
Cmain.c · 81 行
#include <stdio.h>#include <stdlib.h> static long long *use, *cap;static int *start, *near; // near[start[v]] … near[start[v + 1] - 1] = v와 전선으로 이어진 차단기들static char *tripped;static int lit = 0;static int *fix, fixCount = 0; static int byNumber(const void *x, const void *y) {    return *(const int *)x - *(const int *)y;} // v를 지나는 전기의 양. 아래쪽부터 정해야 하므로 내려갔다 올라오면서 더한다static long long load(int v, int parent) {    long long total = use[v]; // 합이 9조까지 커지므로 int로는 모자란다    for (int i = start[v]; i < start[v + 1]; i++) {        int w = near[i];        if (w != parent) {            long long below = load(w, v);            if (!tripped[w]) total += below; // 내려간 차단기 아래로는 전기가 흐르지 않는다        }    }    tripped[v] = total > cap[v];    return total;} // 1번에서 내려가며 전기가 오는 곳을 세고, 맨 먼저 올릴 차단기를 적는다static void walk(int v, int parent) {    if (tripped[v]) {        fix[fixCount++] = v; // 이 아래는 어차피 전기가 안 오니 내려가지 않는다        return;    }    lit++;    for (int i = start[v]; i < start[v + 1]; i++) {        if (near[i] != parent) walk(near[i], v);    }} int main(void) {    int n;    if (scanf("%d", &n) != 1) return 0;    use = malloc((n + 1) * sizeof(long long));    cap = malloc((n + 1) * sizeof(long long));    for (int v = 1; v <= n; v++) scanf("%lld", &use[v]);    for (int v = 1; v <= n; v++) scanf("%lld", &cap[v]); // 용량은 10조까지라 %lld로 읽는다     // 전선은 방향 없이 주어진다. 먼저 차단기마다 전선 수를 세어 자리를 잡고, 그 자리에 채운다    int edges = n - 1;    int *a = malloc((edges + 1) * sizeof(int)), *b = malloc((edges + 1) * sizeof(int));    start = calloc(n + 2, sizeof(int));    for (int i = 0; i < edges; i++) {        scanf("%d %d", &a[i], &b[i]);        start[a[i] + 1]++;        start[b[i] + 1]++;    }    for (int v = 1; v <= n + 1; v++) start[v] += start[v - 1];    near = malloc((2 * edges + 1) * sizeof(int));    int *fill = malloc((n + 2) * sizeof(int));    for (int v = 0; v <= n + 1; v++) fill[v] = start[v];    for (int i = 0; i < edges; i++) {        near[fill[a[i]]++] = b[i];        near[fill[b[i]]++] = a[i];    }    // 번호가 작은 아래쪽 차단기부터 본다    for (int v = 1; v <= n; v++) qsort(near + start[v], start[v + 1] - start[v], sizeof(int), byNumber);     tripped = calloc(n + 1, 1);    fix = malloc((n + 1) * sizeof(int));    long long rootLoad = load(1, 0);    walk(1, 0);    printf("%d\n%lld\n", lit, rootLoad);    if (fixCount == 0) {        printf("-\n");    } else {        for (int i = 0; i < fixCount; i++) printf(i > 0 ? " %d" : "%d", fix[i]);        printf("\n");    }    free(use); free(cap); free(a); free(b); free(start); free(near); free(fill); free(tripped); free(fix);    return 0;}
Craykonmain.cray · 72 行
// v를 지나는 전기의 양. 아래쪽부터 정해야 하므로 내려갔다 올라오면서 더한다전기량(v, 위){    total = use[v]; // 9조까지 커져도 2^53 안이라 정확하다    遍历 (w 在 near[v])    {        如果 (w != 위)        {            below = 전기량(w, v);            如果 (!tripped[w])            {                total += below;            }        }    }    tripped[v] = total > cap[v];    返回 total;} // 1번에서 내려가며 전기가 오는 곳을 세고, 맨 먼저 올릴 차단기를 적는다걷기(v, 위){    如果 (tripped[v])    {        fix.添加(v + 1); // 이 아래는 어차피 전기가 안 오니 내려가지 않는다        返回;    }    lit += 1;    遍历 (w 在 near[v])    {        如果 (w != 위)        {            걷기(w, v);        }    }} n = 控制台.读行().转数字();// 목록은 10,000칸까지라 차단기 번호에서 1을 빼 0번부터 둔다use = 控制台.读数字();cap = 控制台.读数字();near = [];循环 (v = 0; v < n; v++){    near.添加([]);}循环 (i = 0; i < n - 1; i++){    선 = 控制台.读数字();    near[선[0] - 1].添加(선[1] - 1);    near[선[1] - 1].添加(선[0] - 1);}遍历 (list 在 near){    list.排序(); // 번호가 작은 아래쪽 차단기부터 본다} tripped = 新列表(n, 假);lit = 0;fix = [];rootLoad = 전기량(0, -1);걷기(0, -1);控制台.输出(lit);控制台.输出(rootLoad);如果 (fix.数量 > 0){    控制台.输出(fix);}否则{    控制台.输出("-");}