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 ) 出发,求解以下两个最长路径:
- 以 ( a ) 为起点且不经过 ( b ) 的最长路径 ( d1 )
- 以 ( 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个子节点,每条边连接两个节点。每个节点都有自己的价值,计算规则如下:
- 若节点N无儿子节点,节点价值为1。
- 若节点N有两个儿子节点,节点价值为两个儿子节点价值之和或按位异或,取决于节点N颜色。红色为价值之和,绿色为价值按位异或。
- 若节点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为根构造一棵树。
-
对于每条边 u-v,其中 v 是 u 的儿子,砍掉 u-v 这条边,将树分为两部分:
- 以 v 为根的子树
- 以 1 为根但砍掉以 v 为根的子树的部分
-
需要知道这两部分的 RGB 节点数量。
-
先进行一次 DFS,统计以每个节点为根的子树中 RGB 的数量。
-
再进行一次 DFS,枚举每条边作为断点,统计两个新部分中的 RGB 数量。
-
检查这两部分是否都是多彩的(包含红、绿、蓝各一个节点),统计符合条件的砍法数量。
-
输出符合条件的砍法数量。
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;
}
}