为了更好的阅读体检,为了更好的阅读体检,,可以查看我的算法学习博客第五题-RGP种树
在线评测链接:P1170
题目描述:
塔子哥是一位著名的冒险家,他经常在各种森林里探险。今天,他来到了道成林,这是一片美丽而神秘的森林。在探险途中,他遇到了一棵 n 个节点的树,树上每个节点都被涂上了红、绿、蓝三种颜色之一。
塔子哥发现,如果这棵树同时存在一个红色节点、一个绿色节点和一个蓝色节点,那么我们就称这棵树是多彩的。很幸运,他发现这棵树最初就是多彩的。
但是,在探险的过程中,塔子哥发现这棵树上有一条边非常重要,如果砍掉这条边,就可以把这棵树分成两个部分。他想知道,有多少种砍法可以砍掉这条边,使得砍完之后形成的两棵树都是多彩的。
输入描述
第一行个整数 n ,表示节点个数
第二行 n-1 个整数 ,
,...,
,
表示树上 i 和 p 两点之间有一条边。保证给出的定是一棵树。
第三行一个长度为 n 的字符串,第 i 个字符表示第 i 个节点的初始颜色。其中 R 表示红色, G 表示绿色, B 表示蓝色。
保证字符串只由这三种大写字母构成对于全部数据, 。
输出描述
输出一行,一个正整数,表示答案。
样例
输入
7 1 2 3 1 5 5 GBGRRBB
输出
1
思路
树的遍历 + DFS
考虑以 1 号点为根构造一棵树。
对于每条边 u-v,其中 v 是 u 的儿子。
则砍掉 u-v 这条边,将树分为了 以 v 为根的子树 和 以 0 为根的树上砍掉以 v 为根的子树 这两部分。
故需要知道这两部分的 RGB 的数量。
先一遍 dfs 统计以每个点为根的子树中 RGB 的数量,再一遍 dfs 枚举断点每条边,然后统计两个新的部分中 RGB 的数量。
时间复杂度:O(n)
类似题目推荐
点评:树上dfs问题在LeetCode上有非常多的例题,并且在2023年春招过程中考了114514次。望周知。
LeetCod
-
589. N 叉树的前序遍历
-
429. N 叉树的层序遍历
-
590. N 叉树的后序遍历
Codefun2000
难度按序号递增,一次刷个够!
1.P1224 携程 2023.04.15-春招-第三题-魔法之树
2.P1159. 2022年清华大学(深圳)保研夏令营机试题-第一题-树上计数
3.P1065. 米哈游 2023.3.5-最长的通讯路径
4.P1196 华为 2023-04-19-第二题-塔子哥出城
5.P1044. 拼多多内推笔试-2023.2.22.投喂珍珠
6.P1193. 腾讯音乐 2023.04.13-暑期实习-第二题-价值二叉树
7.P1081. 百度 2023.3.13-第三题-树上同色连通块
更多请见:知识点分类-训练-深度优先搜索专栏
代码
CPP
#include <bits/stdc++.h>
using namespace std;
// 将字母映射到一个ID
int idx(char ch) {
if (ch == 'R') return 0;
else if (ch == 'G') return 1;
return 2;
}
// 判断两个子树 A 和删掉了子树 A 的树中每个颜色是否都还存在
bool check(vector<int>& A, vector<int>& B) {
for (int i = 0; i < 3; ++i) {
if (A[i] == 0 || B[i] == A[i]) {
return false;
}
}
return true;
}
int main()
{
int n;
cin >> n;
vector<vector<int>> g(n);
for (int i = 2; i <= n; ++i) {
int p; cin >> p;
g[i - 1].emplace_back(p - 1);
g[p - 1].emplace_back(i - 1);
}
string s;
cin >> s;
vector<vector<int>> cnt(n, vector<int>(3, 0));
int ans = 0;
// first = true 表示是在统计整个树的 RGB
// first = false 表示是在统计答案
function<void(int,int,bool)> dfs = [&](int u, int fa, bool first) {
for (int v: g[u]) {
if (v == fa) continue;
dfs(v, u, first);
if (first) {
// 累加子树中的所有 RGB
for (int j = 0; j < 3; ++j) cnt[u][j] += cnt[v][j];
} else {
// 如果断掉 u-v 这条边,使得两棵树都有 RGB,则答案 + 1
if (check(cnt[v], cnt[0])) ans += 1;
}
}
if (first) cnt[u][idx(s[u])] += 1;
};
dfs(0, -1, true);
dfs(0, -1, false);
cout << ans << "\n";
return 0;
}
python
# 将字母映射到一个ID
def idx(ch):
if ch == 'R':
return 0
elif ch == 'G':
return 1
return 2
# 判断两个子树 A 和删掉了子树 A 的树中每个颜色是否都还存在
def check(A, B):
for i in range(3):
if A[i] == 0 or B[i] == A[i]:
return False
return True
n = int(input())
g = [[] for _ in range(n)]
p = list(map(int, input().split(" ")))
for i in range(2, n + 1):
g[i - 1].append(p[i - 2] - 1)
g[p[i - 2] - 1].append(i - 1)
s = input()
cnt = [[0] * 3 for _ in range(n)]
ans = 0
# first = true 表示是在统计整个树的 RGB
# first = false 表示是在统计答案
def dfs(u, fa, first):
global ans
for v in g[u]:
if v == fa:
continue
dfs(v, u, first)
if first:
# 累加子树中的所有 RGB
for j in range(3):
cnt[u][j] += cnt[v][j]
else:
# 如果断掉 u-v 这条边,使得两棵树都有 RGB,则答案 + 1
if check(cnt[v], cnt[0]):
ans += 1
if first:
cnt[u][idx(s[u])] += 1
dfs(0, -1, True)
dfs(0, -1, False)
print(ans)
Java
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();
int[][] cnt = new int[n][3];
// first = true 表示是在统计整个树的 RGB
// first = false 表示是在统计答案
dfs(0, -1, true, g, s, cnt);
ans = 0;
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) {
// 累加子树中的所有 RGB
for (int j = 0; j < 3; ++j) cnt[u][j] += cnt[v][j];
} else {
// 如果断掉 u-v 这条边,使得两棵树都有 RGB,则答案 + 1
if (check(cnt[v], cnt[0])) ans += 1;
}
}
if (first) cnt[u][idx(s.charAt(u))] += 1;
}
}
Go
package main
import (
"fmt"
)
// 将字母映射到一个ID
func idx(ch byte) int {
if ch == 'R' {
return 0
} else if ch == 'G' {
return 1
}
return 2
}
// 判断两个子树 A 和删掉了子树 A 的树中每个颜色是否都还存在
func check(A []int, B []int) bool {
for i := 0; i < 3; i++ {
if A[i] == 0 || B[i] == A[i] {
return false
}
}
return true
}
func main() {
var n int
fmt.Scan(&n)
g := make([][]int, n)
for i := 2; i <= n; i++ {
var p int
fmt.Scan(&p)
g[i-1] = append(g[i-1], p-1)
g[p-1] = append(g[p-1], i-1)
}
var s string
fmt.Scan(&s)
cnt := make([][]int, n)
for i := range cnt {
cnt[i] = make([]int, 3)
}
ans := 0
// first = true 表示是在统计整个树的 RGB
// first = false 表示是在统计答案
var dfs func(u, fa int, first bool)
dfs = func(u, fa int, first bool) {
for _, v := range g[u] {
if v == fa {
continue
}
dfs(v, u, first)
if first {
// 累加子树中的所有 RGB
for j := 0; j < 3; j++ {
cnt[u][j] += cnt[v][j]
}
} else {
// 如果断掉 u-v 这条边,使得两棵树都有 RGB,则答案 + 1
if check(cnt[v], cnt[0]) {
ans += 1
}
}
}
if first {
cnt[u][idx(s[u])] += 1
}
}
dfs(0, -1, true)
dfs(0, -1, false)
fmt.Println(ans)
}
Js
process.stdin.resume();
process.stdin.setEncoding('utf-8');
let input = '';
process.stdin.on('data', (data) => {
input += data;
return;
});
process.stdin.on('end', () => {
const lines = input.trim().split('\n');
const n = parseInt(lines[0]);
const g = new Array(n).fill(0).map(() => []);
p = lines[1].split(' ').map(x => parseInt(x));
for (let i = 2; i <= n; i++) {
g[i - 1].push(p[i - 2] - 1);
g[p[i - 2] - 1].push(i - 1);
}
const s = lines[2].trim();
const cnt = new Array(n).fill(0).map(() => new Array(3).fill(0));
let ans = 0;
// first = true 表示是在统计整个树的 RGB
// first = false 表示是在统计答案
const dfs = (u, fa, first) => {
for (let i = 0; i < g[u].length; i++) {
const v = g[u][i];
if (v === fa) continue;
dfs(v, u, first);
if (first) {
// 累加子树中的所有 RGB
for (let j = 0; j < 3; j++) {
cnt[u][j] += cnt[v][j];
}
} else {
// 如果断掉 u-v 这条边,使得两棵树都有 RGB,则答案 + 1
if (check(cnt[v], cnt[0])) ans += 1;
}
}
if (first) cnt[u][idx(s[u])] += 1;
};
dfs(0, -1, true);
dfs(0, -1, false);
console.log(ans);
});
// 将字母映射到一个ID
function idx(ch) {
if (ch === 'R') return 0;
else if (ch === 'G') return 1;
return 2;
}
// 判断两个子树 A 和删掉了子树 A 的树中每个颜色是否都还存在
function check(A, B) {
for (let i = 0; i < 3; i++) {
if (A[i] === 0 || B[i] === A[i]) {
return false;
}
}
return true;
}



![[C++刷题之旅]反转链表](https://img-blog.csdnimg.cn/b51033cc0360463f80e1dd2c2e0ba3ef.png)











