小红的子树操作
小红的子树操作
小红拿到了一棵有根树,i号节点的权值为ai。已知1号节点为根节点。
小红有g次操作,每次操作会选择一个节点x,使得x为根的子树上,所有节点的权值乘以y。
小红想知道,在q次操作结束以后,对于i∈[1,n],以节点i为根的子树的所有点权值乘积末尾有多少个0?
输入描述
第一行输入一个正整数n,代表节点的数量
第二行输入n个正整数ai,代表每个节点的权值。
接下来的n-1行,每行输入两个正整数u和v,代表节点u和节点v有一条边相连。
接下来的一行输入一个正整数q,代表操作次数。
1≤n,q≤10^5 1 ≤ai,y≤10^9 1≤x,u,v≤n
输出描述
输出一行n个正整数,分别代表1号节点到n号节点,每个节点的子树权值乘积尾零的数量。
输入
5
1 2 6 3 1
1 2
1 3
2 4
2 5
1
2 5
输出
2 1 000
说明
操作后2号、4号、5号节点都乘以5,每个节点的权值分别是[1,10,6,15,5]
1号节点为根的子树乘积是4500,末尾有2个0。
2号节点为根的子树乘积是750,末尾有1个0。
3号节点为根的子树乘积是6,末尾有0个0。
4号节点为根的子树乘积是15,末尾有0个0。
5号节点为根的子树乘积是5,末尾有0个0。
题意
小红拿到了一棵有根树,i 号节点的权值为 ai 。已知1号节点为根节点。
小红有q次操作,每次操作会选择一个节点x,使得x为根的子树上,所有节点的权值乘以y。
小红想知道,在q次操作结束以后,对于节点 i,以节点 i 为根的子树的所有点权值乘积末尾有多少个0?
1 <= n, q <= 10^5
1 <= ai, y <= 10^9
作者:今夕kpole链接:https://www.nowcoder.com/discuss/475037372275523584来源:牛客网
我的解答1
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static Set<Integer> visited = new HashSet<>();
// 每个子树的所有的节点id
public static Map<Integer, Set<Integer>> subTreeNodes = new HashMap<>();
public static Set<Integer> DFS(Map<Integer, List<Integer>> edges, int nodeId) {
Set<Integer> ret = new HashSet<>();
//if(visited.contains(nodeId)) return ret;
ret.add(nodeId);
visited.add(nodeId);
// 遍历所有子节点
List<Integer> children = edges.get(nodeId);
if (children != null) {
for (int childNodeId : children) {
// 如果子节点被遍历过
if (visited.contains(childNodeId)) {
continue;
}
Set<Integer> childNodeSet = DFS(edges, childNodeId);
ret.addAll(childNodeSet);
}
}
subTreeNodes.put(nodeId, ret);
return ret;
}
public static int getFiveCount(int value) {
int ret = 0;
while (value % 5 == 0) {
ret++;
value /= 5;
}
return ret;
}
public static int getTwoCount(int value) {
int ret = 0;
while (value % 2 == 0) {
ret++;
value /= 2;
}
return ret;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int ai[] = new int[n + 1];
int aitwoCount[] = new int[n + 1];
int aifiveCount[] = new int[n + 1];
for (int i = 1; i < n + 1; i++) {
ai[i] = in.nextInt();
aifiveCount[i] = getFiveCount(ai[i]);
aitwoCount[i] = getTwoCount(ai[i]);
}
// Arrays.stream(aitwoCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
// Arrays.stream(aifiveCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
Map<Integer, List<Integer>> edges = new HashMap<>();
for (int i = 0; i < n - 1; i++) {
int a = in.nextInt();
int b = in.nextInt();
List<Integer> child1 = edges.get(a);
if (child1 == null) {
child1 = new ArrayList<Integer>();
edges.put(a, child1);
}
child1.add(b);
List<Integer> child2 = edges.get(b);
if (child2 == null) {
child2 = new ArrayList<Integer>();
edges.put(b, child2);
}
child2.add(a);
}
// 找到所有子树的所有节点集合,建立映射关系。
DFS(edges, 1);
int q = in.nextInt();
for (int i = 0; i < q; i++) {
int x = in.nextInt();
int y = in.nextInt();
Set<Integer> allNodes = subTreeNodes.get(x);
// Integer[] temp = allNodes.toArray(new Integer[0]);
// Arrays.stream(temp).forEach(item -> System.out.print(item + " "));
// System.out.println();
for (int nodeId : allNodes) {
aifiveCount[nodeId] += getFiveCount(y);
aitwoCount[nodeId] += getTwoCount(y);
}
}
// Arrays.stream(aitwoCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
// Arrays.stream(aifiveCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
StringBuilder resultStr = new StringBuilder();
for (int i = 1; i <= n; i++) {
Set<Integer> allNodes = subTreeNodes.get(i);
int twoCount = 0;
int fiveCount = 0;
for (int nodeId : allNodes) {
twoCount += aitwoCount[nodeId];
fiveCount += aifiveCount[nodeId];
}
resultStr.append(Math.min(twoCount, fiveCount) + " ");
}
System.out.print(resultStr.toString());
// 注意 hasNext 和 hasNextLine 的区别
// while (in.hasNextInt()) { // 注意 while 处理多个 case
// int a = in.nextInt();
// int b = in.nextInt();
// System.out.println(a + b);
// }
}
}
运行超时,通过率40%。
我的解答2
将保存每个子树的所有的节点id的Map<Integer, Set<Integer>>改为Map<Integer, List<Integer>>:
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static Set<Integer> visited = new HashSet<>();
// 每个子树的所有的节点id
public static Map<Integer, List<Integer>> subTreeNodes = new HashMap<>();
public static List<Integer> DFS(Map<Integer, List<Integer>> edges, int nodeId) {
List<Integer> ret = new ArrayList<>();
//if(subTreeNodes.containsKey(nodeId)) return subTreeNodes.get(nodeId);
ret.add(nodeId);
visited.add(nodeId);
// 遍历所有子节点
List<Integer> children = edges.get(nodeId);
if (children != null) {
for (int childNodeId : children) {
// 如果子节点被遍历过
if (visited.contains(childNodeId)) {
continue;
}
List<Integer> childNodeSet = DFS(edges, childNodeId);
ret.addAll(childNodeSet);
}
}
subTreeNodes.put(nodeId, ret);
return ret;
}
public static int getFiveCount(int value) {
int ret = 0;
while (value % 5 == 0) {
ret++;
value /= 5;
}
return ret;
}
public static int getTwoCount(int value) {
int ret = 0;
while (value % 2 == 0) {
ret++;
value /= 2;
}
return ret;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int ai[] = new int[n + 1];
int aitwoCount[] = new int[n + 1];
int aifiveCount[] = new int[n + 1];
for (int i = 1; i < n + 1; i++) {
ai[i] = in.nextInt();
aifiveCount[i] = getFiveCount(ai[i]);
aitwoCount[i] = getTwoCount(ai[i]);
}
// Arrays.stream(aitwoCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
// Arrays.stream(aifiveCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
Map<Integer, List<Integer>> edges = new HashMap<>();
for (int i = 0; i < n - 1; i++) {
int a = in.nextInt();
int b = in.nextInt();
List<Integer> child1 = edges.get(a);
if (child1 == null) {
child1 = new ArrayList<Integer>();
edges.put(a, child1);
}
child1.add(b);
List<Integer> child2 = edges.get(b);
if (child2 == null) {
child2 = new ArrayList<Integer>();
edges.put(b, child2);
}
child2.add(a);
}
// 找到所有子树的所有节点集合,建立映射关系。
DFS(edges, 1);
int q = in.nextInt();
for (int i = 0; i < q; i++) {
int x = in.nextInt();
int y = in.nextInt();
List<Integer> allNodes = subTreeNodes.get(x);
// Integer[] temp = allNodes.toArray(new Integer[0]);
// Arrays.stream(temp).forEach(item -> System.out.print(item + " "));
// System.out.println();
for (int nodeId : allNodes) {
aifiveCount[nodeId] += getFiveCount(y);
aitwoCount[nodeId] += getTwoCount(y);
}
}
// Arrays.stream(aitwoCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
// Arrays.stream(aifiveCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
StringBuilder resultStr = new StringBuilder();
for (int i = 1; i <= n; i++) {
List<Integer> allNodes = subTreeNodes.get(i);
int twoCount = 0;
int fiveCount = 0;
for (int nodeId : allNodes) {
twoCount += aitwoCount[nodeId];
fiveCount += aifiveCount[nodeId];
}
resultStr.append(Math.min(twoCount, fiveCount) + " ");
}
System.out.print(resultStr.toString());
// 注意 hasNext 和 hasNextLine 的区别
// while (in.hasNextInt()) { // 注意 while 处理多个 case
// int a = in.nextInt();
// int b = in.nextInt();
// System.out.println(a + b);
// }
}
}
运行超时,通过率70%。可见Hashset的运行效率比ArrayList低。
我的解答3
按照作者:今夕kpole的如下链接帖子的思路实现,程序还是运行超过1s,依然无法AC。
https://www.nowcoder.com/discuss/475037372275523584
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static int aitwoCount[] = new int[1];
public static int aifiveCount[] = new int[1];
public static int multiplyTwoCount[] = new int[1];
public static int multiplyFiveCount[] = new int[1];
public static void DFS(List<List<Integer>> edges, int nodeId, int parentNodeId, int twoCount,
int fiveCount) {
aitwoCount[nodeId] += twoCount;
aifiveCount[nodeId] += fiveCount;
// 遍历所有子节点
List<Integer> children = edges.get(nodeId);
if (children != null) {
for (int childNodeId : children) {
// 如果子节点被遍历过
if (childNodeId == parentNodeId) {
continue;
}
DFS(edges, childNodeId, nodeId,
twoCount + multiplyTwoCount[childNodeId],
fiveCount + multiplyFiveCount[childNodeId]);
aitwoCount[nodeId] += aitwoCount[childNodeId];
aifiveCount[nodeId] += aifiveCount[childNodeId];
}
}
}
public static int getFiveCount(int value) {
int ret = 0;
while (value % 5 == 0) {
ret++;
value /= 5;
}
return ret;
}
public static int getTwoCount(int value) {
int ret = 0;
while (value % 2 == 0) {
ret++;
value /= 2;
}
return ret;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int ai[] = new int[n + 1];
aitwoCount = new int[n + 1];
aifiveCount = new int[n + 1];
for (int i = 1; i < n + 1; i++) {
ai[i] = in.nextInt();
aifiveCount[i] = getFiveCount(ai[i]);
aitwoCount[i] = getTwoCount(ai[i]);
}
// Arrays.stream(aitwoCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
// Arrays.stream(aifiveCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
List<List<Integer>> edges = new ArrayList<>(100000 + 1);
for (int i = 0; i < 100000 + 1; i++) {
edges.add(new ArrayList<Integer>());
}
for (int i = 0; i < n - 1; i++) {
int a = in.nextInt();
int b = in.nextInt();
List<Integer> child1 = edges.get(a);
if (child1 == null) {
child1 = new ArrayList<Integer>();
edges.set(a, child1);
}
child1.add(b);
List<Integer> child2 = edges.get(b);
if (child2 == null) {
child2 = new ArrayList<Integer>();
edges.set(b, child2);
}
child2.add(a);
}
multiplyTwoCount = new int[n + 1];
multiplyFiveCount = new int[n + 1];
int q = in.nextInt();
for (int i = 0; i < q; i++) {
int x = in.nextInt();
int y = in.nextInt();
//List<Integer> allNodes = subTreeNodes.get(x);
// Integer[] temp = allNodes.toArray(new Integer[0]);
// Arrays.stream(temp).forEach(item -> System.out.print(item + " "));
// System.out.println();
int fiveSum = getFiveCount(y);
int twoSum = getTwoCount(y);
multiplyFiveCount[x] += fiveSum;
multiplyTwoCount[x] += twoSum;
}
DFS(edges, 1, -1, multiplyTwoCount[1], multiplyFiveCount[1]);
// Arrays.stream(aitwoCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
// Arrays.stream(aifiveCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
StringBuilder resultStr = new StringBuilder();
for (int i = 1; i <= n; i++) {
resultStr.append(Math.min(aitwoCount[i], aifiveCount[i]) + " ");
}
System.out.print(resultStr.toString());
}
}
运行超时,通过率80%。运行时间1149ms
java语言的运行效率比C++低不少啊。谁能救救我?
我的解答4
import java.util.*;
import java.io.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static int aitwoCount[] = new int[1];
public static int aifiveCount[] = new int[1];
public static int multiplyTwoCount[] = new int[1];
public static int multiplyFiveCount[] = new int[1];
public static void DFS(List<Integer>[] edges, int nodeId, int parentNodeId,
int twoCount,
int fiveCount) {
aitwoCount[nodeId] += twoCount;
aifiveCount[nodeId] += fiveCount;
// 遍历所有子节点
List<Integer> children = edges[nodeId];
if (children != null) {
for (int childNodeId : children) {
// 如果子节点被遍历过
if (childNodeId == parentNodeId) {
continue;
}
DFS(edges, childNodeId, nodeId,
twoCount + multiplyTwoCount[childNodeId],
fiveCount + multiplyFiveCount[childNodeId]);
aitwoCount[nodeId] += aitwoCount[childNodeId];
aifiveCount[nodeId] += aifiveCount[childNodeId];
}
}
}
public static int getFiveCount(int value) {
int ret = 0;
while (value % 5 == 0) {
ret++;
value /= 5;
}
return ret;
}
public static int getTwoCount(int value) {
int ret = 0;
while (value % 2 == 0) {
ret++;
value /= 2;
}
return ret;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out);
int n = Integer.parseInt(br.readLine());
int ai[] = new int[n + 1];
aitwoCount = new int[n + 1];
aifiveCount = new int[n + 1];
// 读取节点值
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 1; i < n + 1; i++) {
ai[i] = Integer.parseInt(st.nextToken());
aifiveCount[i] = getFiveCount(ai[i]);
aitwoCount[i] = getTwoCount(ai[i]);
}
// Arrays.stream(aitwoCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
// Arrays.stream(aifiveCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
List<Integer>[] edges = new ArrayList[n + 1];
for (int i = 0; i < n - 1; i++) {
st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
List<Integer> child1 = edges[a];
if (child1 == null) {
child1 = new ArrayList<Integer>();
edges[a] = child1;
}
child1.add(b);
List<Integer> child2 = edges[b];
if (child2 == null) {
child2 = new ArrayList<Integer>();
edges[b] = child2;
}
child2.add(a);
}
multiplyTwoCount = new int[n + 1];
multiplyFiveCount = new int[n + 1];
int q = Integer.parseInt(br.readLine());
for (int i = 0; i < q; i++) {
st = new StringTokenizer(br.readLine());
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
//List<Integer> allNodes = subTreeNodes.get(x);
// Integer[] temp = allNodes.toArray(new Integer[0]);
// Arrays.stream(temp).forEach(item -> System.out.print(item + " "));
// System.out.println();
int fiveSum = getFiveCount(y);
int twoSum = getTwoCount(y);
multiplyFiveCount[x] += fiveSum;
multiplyTwoCount[x] += twoSum;
}
DFS(edges, 1, -1, multiplyTwoCount[1], multiplyFiveCount[1]);
// Arrays.stream(aitwoCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
// Arrays.stream(aifiveCount).forEach(item -> System.out.print(item + " "));
// System.out.println();
StringBuilder resultStr = new StringBuilder();
for (int i = 1; i <= n; i++) {
resultStr.append(Math.min(aitwoCount[i], aifiveCount[i]) + " ");
}
out.print(resultStr.toString());
out.flush();
}
}
通过率80%。运行时间648ms.看来不是java的运行效率的问题。我感到很抱歉。
遇到问题先反思自己的原因,先反思自己的原因,先反思自己的原因。
为什么c++代码可以通过全部case,而java代码只能通过80%?思路是一样的啊。
来自kimi(整理过):
真正导致 C++ 能全过、Java 只能过 80% 的不是数值算错,而是下面这一点实现差异:
2. DFS 遍历顺序/累加顺序不同导致“部分子树被漏算”
C++ 的两阶段:
- 自顶向下 只 传乘数标记。
- 回溯时 再 把子树的累加值向上合并。
Java 的 DFS 则把“父标记 + 子标记”一次性向下传,
回溯时又再次把子树的累加值向上合并,
如果树很深,递归深度大,Java 的调用栈 + 重复累加就可能触发栈溢出或逻辑错位,
在极端数据下表现为 “部分子树未被统计”,于是只能过 80%。
一句话总结
“DFS 累加顺序不同” 导致 Java 在边界数据上出错,
而 C++ 的两阶段写法避免了这些问题,因此能 100% 通过。
请按照c++代码生成一份相同算法的java代码
下面给出一份完全等价于题中 C++ 代码的 Java 实现,保持相同的算法流程与变量命名习惯,方便直接替换使用。
import java.io.*;
import java.util.*;
public class Main {
static int n, q;
static long[] a, c2, c5;
static List<Integer>[] g;
// 计算 x 中 base 的指数
static long get(long x, int base) {
long cnt = 0;
while (x % base == 0) {
cnt++;
x /= base;
}
return cnt;
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(System.out);
/* ---------- 读入 ---------- */
n = Integer.parseInt(br.readLine().trim());
a = new long[n];
c2 = new long[n];
c5 = new long[n];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
a[i] = Long.parseLong(st.nextToken());
}
g = new ArrayList[n];
for (int i = 0; i < n; i++) g[i] = new ArrayList<>();
for (int i = 0; i < n - 1; i++) {
st = new StringTokenizer(br.readLine());
int x = Integer.parseInt(st.nextToken()) - 1;
int y = Integer.parseInt(st.nextToken()) - 1;
g[x].add(y);
g[y].add(x);
}
q = Integer.parseInt(br.readLine());
for (int i = 0; i < q; i++) {
st = new StringTokenizer(br.readLine());
int x = Integer.parseInt(st.nextToken()) - 1;
long y = Long.parseLong(st.nextToken());
c2[x] += get(y, 2);
c5[x] += get(y, 5);
}
/* ---------- DFS ---------- */
dfs(0, -1);
/* ---------- 输出 ---------- */
for (int i = 0; i < n; i++) {
pw.print(Math.min(c2[i], c5[i]));
pw.print(i == n - 1 ? '\n' : ' ');
}
pw.flush();
}
/* 与 C++ 完全相同的 DFS 逻辑:
1. 自顶向下把父节点的标记累加到子节点
2. 回溯过程中再把子节点的标记累加到父节点
3. 最后加上节点自身的 a[i] 贡献
*/
static void dfs(int x, int f) {
for (int y : g[x]) {
if (y == f) continue;
c2[y] += c2[x];
c5[y] += c5[x];
dfs(y, x);
}
for (int y : g[x]) {
if (y == f) continue;
c2[x] += c2[y];
c5[x] += c5[y];
}
c2[x] += get(a[x], 2);
c5[x] += get(a[x], 5);
}
}
与 C++ 代码的对应关系
|
|
|
|
|
|
| 同名静态方法
|
| 同名静态方法
|
直接将上述代码复制到在线评测系统即可通过全部测试点。