dfs-tree

华为20220923

题目描述

有一家云存储服务提供商,他们的存储系统采用主从模式以确保高可用性。当主节点发生故障时,系统会自动切换到备用节点。为了保证系统的稳定性,需要检测服务状态,并在必要时触发主备切换。

存储系统中的服务有依赖关系,每个服务最多依赖一个其他服务,且依赖关系不成环。某个服务故障时,其依赖的服务也会受到影响,可能导致更多服务故障。

需要确定检测顺序,以最大限度减少故障对业务的影响。首先检测对业务影响最大的服务,如果影响相同,则按节点编号升序检测。

输入描述

第一行输入两个整数 (n),代表业务节点总个数(1 ≤ (n) ≤ 100000)。
接下来一行输入 (n) 个整数,第 (i) 个整数 (f_i) 代表 (i) 依赖 (f_i)(0 ≤ (i) < (n))。
若 (f_i) = -1,则表示节点 (i) 没有依赖。
数据保证 (f_i) ≠ (i)。

输出描述

一行输出 (n) 个整数,以空格隔开,代表最终的检测顺序。

示例

输入

5
-1 -1 1 2 3

输出

4 3 2 1 0

思路:DFS + 排序

  • (f_i) 代表 (i) 依赖 (f_i)
  • 抽象为 边 (f_i \rightarrow i)
  • 排序规则:
    • 子树节点数不同:节点数多的影响大,排序靠前
    • 子树节点数相同:按节点编号升序检测
import java.util.*;
import java.util.stream.IntStream;

public class Main {
    static int n; // 节点总数
    static int[] w, f, d, p; // w: 依赖数组, f: 子节点数量数组, d: 入度数组, p: 节点编号数组
    static List<Integer>[] g; // 邻接表表示的图

    // 深度优先搜索计算每个节点的子节点数量
    public static int dfs(int u) {
        f[u] = 1; // 每个节点自身算一个
        for (int x : g[u]) { // 遍历所有子节点
            f[u] += dfs(x); // 递归计算子节点的数量并累加
        }
        return f[u];
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt(); // 输入节点总数
        w = new int[n]; // 依赖数组
        f = new int[n]; // 子节点数量数组
        d = new int[n]; // 入度数组
        p = new int[n]; // 节点编号数组
        g = new ArrayList[n]; // 初始化邻接表
        for (int i = 0; i < n; i++) {
            w[i] = sc.nextInt(); // 输入依赖节点
            g[i] = new ArrayList<>(); // 初始化每个节点的邻接表
        }

        // 构建图的邻接表和入度数组
        for (int i = 0; i < n; i++) {
            p[i] = i; // 初始化节点编号数组
            if (w[i] != -1) { // 如果节点有依赖
                d[i]++; // 增加入度
                g[w[i]].add(i); // 在依赖节点中添加当前节点
            }
        }

        // 对所有入度为0的节点进行DFS
        for (int i = 0; i < n; i++) {
            if (d[i] == 0) {
                dfs(i); // 计算每个节点的子节点数量
            }
        }

        // 将节点编号数组转换为Integer数组,以便进行排序
        Integer[] pWrapper = IntStream.of(p).boxed().toArray(Integer[]::new);

        // 根据子节点数量降序排序,如果子节点数量相同则按节点编号升序排序
        Arrays.sort(pWrapper, (a, b) -> {
            if (f[a] != f[b]) return f[a] > f[b] ? -1 : 1;
            return a - b;
        });

        // 输出排序后的节点编号
        for (int i = 0; i < n; i++) {
            System.out.print(pWrapper[i] + " ");
        }
    }
}

mhy2023081302

题目描述

给定一个有 (n) 个节点的树,树根编号为 1。你可以在叶子节点上添加新的儿子节点。经过若干次操作后,求距离树根不超过 (k) 的节点最多有多少个。

输入描述

第一行,一个正整数 (n) 表示树中节点的个数,(k) 表示距离不超过树根的距离。接下来的 (n-1) 行,每行包含两个整数 (u) 和 (v),表示节点 (u) 和 (v) 之间有一条边。

输出描述

一个整数,表示若干次操作后距离树根不超过 (k) 的节点最大数量。

示例

输入

4 2  
1 2  
1 3  
1 4  

输出

7

思路:DFS + 贡献计数法

  • 考虑每个节点对答案的贡献:如果当前节点距离根节点的距离 (d) 满足 (d < k),则答案加1。
  • 此外,如果当前节点是叶子节点,则可以在该叶子节点下添加 (k-d) 个节点,形成一条链。根据上述方法统计即可。注意题目给出的边是无向边,需要将其视为两条有向边。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

/**
 * @description: Program to perform a depth-first search (DFS) on a tree structure
 * to calculate a specific result based on node distances and a given threshold k.
 * The exact nature of the problem is inferred from the code structure.
 * Assumes nodes are numbered from 1 to n in the input.
 * 
 * Author: wenLiu
 * Created: 2024/5/23
 */
public class Main {

    static int n, k; // Number of nodes and threshold value k
    static List<List<Integer>> g; // Adjacency list for the graph (tree)

    static int[] d; // Array to store distances of nodes from the root
    static long res; // Result variable to store the final output

    public static void main(String[] args) throws IOException {
        // Reading input using BufferedReader for efficient input handling
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer tokenizer = new StringTokenizer(reader.readLine());
        
        // Reading number of nodes (n) and threshold value (k)
        n = Integer.parseInt(tokenizer.nextToken());
        k = Integer.parseInt(tokenizer.nextToken());

        // Initializing the adjacency list for n nodes
        g = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<Integer>());
        }

        // Reading the edges of the tree
        for (int i = 1; i < n; i++) {
            StringTokenizer stringTokenizer = new StringTokenizer(reader.readLine());
            int u = Integer.parseInt(stringTokenizer.nextToken()) - 1; // Node u
            int v = Integer.parseInt(stringTokenizer.nextToken()) - 1; // Node v
            g.get(u).add(v); // Adding edge u-v
            g.get(v).add(u); // Adding edge v-u (since it's an undirected graph)
        }

        // Initializing distance array with a large value (infinity)
        d = new int[n];
        Arrays.fill(d, Integer.MAX_VALUE);
        d[0] = 0; // Distance of root node (assumed to be node 0) is 0
        res = 0; // Initializing result variable

        // Starting DFS from the root node (node 0) with no parent (-1)
        dfs(0, -1);

        // Output the final result
        System.out.println(res);
    }

    // Depth-first search function to explore the tree
    private static void dfs(int u, int p) {
        // If the distance of node u is less than or equal to k, increment result
        if (d[u] <= k) {
            res++;
        }

        // If node u is a leaf node and its distance is less than k,
        // add (k - d[u]) to the result
        if (u != 0 && g.get(u).size() == 1 && d[u] < k) {
            res += (k - d[u]);
        }

        // Traverse all adjacent nodes (children) of node u
        for (int v : g.get(u)) {
            // Skip the parent node to avoid cycling back
            if (v == p) {
                continue;
            }

            // Set the distance of node v and recursively call DFS
            d[v] = d[u] + 1;
            dfs(v, u);
        }
    }
}

meituan2023040802

题目描述

给定一棵有 ( n ) 个节点的树和一条指定的边,找到所有经过这条边的简单路径中最长的一条路径的长度。简单路径的长度定义为路径上的边的数量。

输入描述

  • 第一行包含一个整数 ( n ),表示树的节点个数。
  • 第二行包含 ( n-1 ) 个整数,其中第 ( i ) 个整数 ( p_i ) 表示节点 ( i+1 ) 和节点 ( p_i ) 之间有一条边。
  • 第三行包含两个整数 ( x ) 和 ( y ),表示指定的边。保证这条边是树上的一条边。

约束条件:

  • ( 2 <= n <= 10^5 )
  • ( 1 <= p_i <= n )
  • ( 1 <= x, y <= n )
  • ( x ≠ y )

输出描述

输出一个整数,表示所有经过指定边的简单路径中最长的一条路径的长度。

示例

输入

7
1 2 3 4 5 3
3 7

输出

4

思路:两次DFS

题目要求的是经过指定边 ( a -> b ) 的最长路径。可以分别从 ( a ) 和 ( b ) 出发,求解以下两个最长路径:

  1. 以 ( a ) 为起点且不经过 ( b ) 的最长路径 ( d1 )
  2. 以 ( b ) 为起点且不经过 ( a ) 的最长路径 ( d2 )

最终的答案就是 ( d1 + d2 + 1 )。

import java.util.*;

public class Main {
private static Scanner scanner = new Scanner(System.in);

private static List<List<Integer>> g;
private static int[] f;

private static void dfs(int u, int fa) {
    f[u] = 0;
    for (int v : g.get(u)) {
        if (v == fa) continue;
        dfs(v, u);
        f[u] = Math.max(f[u], f[v] + 1);
    }
}

private static void solve() {
    int n = scanner.nextInt();
    g = new ArrayList<>();
    for (int i = 0; i <= n; i++) g.add(new ArrayList<>());
    for (int i = 1; i < n; i++) {
        int x = scanner.nextInt(), y = i + 1;
        g.get(x).add(y);
        g.get(y).add(x);
    }
    int a = scanner.nextInt(), b = scanner.nextInt();
    f = new int[n + 1];
    dfs(a, b);
    int res = f[a];
    dfs(b, a);
    res += f[b];
    System.out.println(res + 1);
}

public static void main(String[] args) {
    int T = 1;
    while (T-- > 0) {
        solve();
    }
}

}

meituan2023040105

研究一种新的树。

该树每个节点有0,1,2个子节点,每条边连接两个节点。每个节点都有自己的价值,计算规则如下:

  1. 若节点N无儿子节点,节点价值为1。
  2. 若节点N有两个儿子节点,节点价值为两个儿子节点价值之和或按位异或,取决于节点N颜色。红色为价值之和,绿色为价值按位异或。
  3. 若节点N只有一个儿子节点,节点价值等于这个儿子的价值。

按位异或用符号⊕表示,对应位不同时为1,否则为0。

例如:

  • 5 = (101)₂
  • 6 = (110)₂
  • 5 ⊕ 6 = 3,即 (101)₂ ⊕ (110)₂ = (011)₂

输入描述

  • 第一行一个正整数 ( n ) 表示节点个数。
  • 第二行 ( n-1 ) 个正整数 ( p[i] )(2≤i≤n)表示第 ( i ) 个节点的父节点,1号节点是根节点。
  • 第三行 ( n ) 个整数 ( c[i] )(1≤i≤n),当 ( c[i]=1 ) 表示第 ( i ) 个节点是红色,( c[i]=2 ) 表示绿色。

数据保证形成合法的树。对于所有数据,( n ≤ 50000 )。

输出描述

输出根节点的值。

示例

输入:

3
1 1
2 2 2

输出:

0

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * @description:TODO
 * @author: wenLiu
 * @create: 2024/5/24 10:22
 */
public class Main {
    static int n;

    static List<List<Integer>> g;

    static int[] c;

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(reader.readLine());

        g = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<Integer>());
        }


        StringTokenizer stringTokenizer = new StringTokenizer(reader.readLine());
        for (int i = 1; i < n; i++) {
            int u = Integer.parseInt(stringTokenizer.nextToken()) - 1;
            int v = i;

            g.get(u).add(v);
        }

        c = new int[n];
        StringTokenizer colorsTokenizer = new StringTokenizer(reader.readLine());
        for (int i = 0; i < n; i++) {
            c[i] = Integer.parseInt(colorsTokenizer.nextToken());
        }

        System.out.println(dfs(0));
    }

    private static int dfs(int u) {
        if (g.get(u).size() == 0) return 1;
        int sum = 0;
        for (Integer v : g.get(u)) {
            if (c[u] == 1) {
                sum += dfs(v);
            } else {
                sum ^= dfs(v);
            }
        }
        return sum;
    }
}

meituan2023040805

给定一棵 ( n ) 个节点的树,每个节点被涂上红、绿、蓝三种颜色之一。

如果树同时存在一个红色节点、一个绿色节点和一个蓝色节点,则称树是多彩的。初始状态下,这棵树是多彩的。

现在,需要砍掉树上的一条边,使得分成的两棵树都是多彩的。求有多少种砍法可以使得分成的两棵树都是多彩的。

输入描述

  • 第一行一个整数 ( n ),表示节点个数。
  • 第二行 ( n-1 ) 个整数 ( p_2, p_3, ... , p_n ),表示节点 ( i ) 和节点 ( p_i ) 之间有一条边。
  • 第三行一个长度为 ( n ) 的字符串,第 ( i ) 个字符表示第 ( i ) 个节点的初始颜色。其中 R 表示红色,G 表示绿色,B 表示蓝色。

保证输入数据构成一棵树,字符串只由这三种大写字母构成。 ( 3 ≤ n ≤ 10^5 )。

输出描述

输出一个正整数,表示答案。

示例

输入:

7
1 2 3 1 5 5
GBGRRBB

输出:

1

思路

  1. 以节点1为根构造一棵树。

  2. 对于每条边 u-v,其中 v 是 u 的儿子,砍掉 u-v 这条边,将树分为两部分:

    • 以 v 为根的子树
    • 以 1 为根但砍掉以 v 为根的子树的部分
  3. 需要知道这两部分的 RGB 节点数量。

  4. 先进行一次 DFS,统计以每个节点为根的子树中 RGB 的数量。

  5. 再进行一次 DFS,枚举每条边作为断点,统计两个新部分中的 RGB 数量。

  6. 检查这两部分是否都是多彩的(包含红、绿、蓝各一个节点),统计符合条件的砍法数量。

  7. 输出符合条件的砍法数量。

import java.util.*;

public class Main {

    static int ans;

    // 根据节点颜色返回对应的索引
    public static int idx(char ch) {
        if (ch == 'R') return 0;
        else if (ch == 'G') return 1;
        return 2;
    }

    // 检查两个节点的颜色是否满足条件(各有一个不同颜色)
    public static boolean check(int[] A, int[] B) {
        for (int i = 0; i < 3; ++i) {
            if (A[i] == 0 || B[i] == A[i]) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();

        // 使用邻接表表示树
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<>());
        }

        // 构建树的邻接表
        for (int i = 2; i <= n; i++) {
            int p = sc.nextInt();
            g.get(i-1).add(p-1);
            g.get(p-1).add(i-1);
        }

        // 读取节点的颜色
        String s = sc.next();

        // 记录每个节点子树中的 RGB 节点数量
        int[][] cnt = new int[n][3];

        // 第一次 DFS 统计子树中的 RGB 节点数量
        // first = true 表示是在统计整个树的 RGB
        // first = false 表示是在统计答案
        dfs(0, -1, true, g, s, cnt);
        ans = 0;

        // 第二次 DFS 统计符合条件的砍法数量
        dfs(0, -1, false, g, s, cnt);

        // 输出结果
        System.out.println(ans);
    }

    // 深度优先搜索
    public static void dfs(int u, int fa, boolean first, List<List<Integer>> g, String s, int[][] cnt) {
        for (int v : g.get(u)) {
            if (v == fa) continue; // 避免重复访问父节点
            dfs(v, u, first, g, s, cnt);
            if (first) {
                // 如果是第一次 DFS,累加子树中的 RGB 节点数量
                for (int j = 0; j < 3; ++j) cnt[u][j] += cnt[v][j];
            } else {
                // 如果是第二次 DFS,检查是否符合条件,答案 + 1
                if (check(cnt[v], cnt[0])) ans += 1;
            }
        }
        // 更新当前节点的 RGB 节点数量
        if (first) cnt[u][idx(s.charAt(u))] += 1;
    }
}
posted @ 2024-05-23 20:30  菠萝包与冰美式  阅读(9)  评论(0编辑  收藏  举报