{ }codelper
深さ優先探索スタックP0292

折りたたまれた返信ツリーの行番号

掲示板の一つの投稿にコメントが n 個付いていて、書かれた順に 1 番から n 番まで番号が付いています。
コメントは投稿に直接付けるか、先に書かれた別のコメントへの返信として付けます。
i 番のコメントについて pᵢ が与えられます。
pᵢ が 0 なら投稿に直接付けたコメントで、そうでなければ pᵢ 番のコメントへの返信です。
返信は必ず番号がより小さいコメントに付けます。
画面にはコメントを 1 行に一つずつ、次の順で表示します。
投稿に直接付けたコメントを番号の小さいものから表示します。
コメントを一つ表示したら、そのコメントへの返信を番号の小さいものからすぐ下に表示します。
返信を一つ表示するときも同じ規則で、その返信に付いた返信まで全部表示してから次の返信に進みます。
コメントごとに折りたたまれているかどうかが与えられます。
cᵢ が 1 なら i 番のコメントは折りたたまれています。
折りたたまれたコメントは自分の 1 行は表示されますが、そのコメントへの返信と、その返信に付いた返信は一つも表示されません。
表示されるコメントには画面の上から 1, 2, 3, … と行番号が付きます。
表示されないコメントは行を取りません。
1 行目に、画面に表示されるコメントの数を出力してください。
2 行目に、1 番から n 番までのコメントの行番号を順に空白一つで区切って出力してください。
表示されないコメントは 0 と出力します。

入力
1 行目に n、2 行目に p₁ … pₙ、3 行目に c₁ … cₙ が空白一つで区切られて与えられます。
1 ≤ n ≤ 10,000、0 ≤ pᵢ < i、cᵢ は 0 か 1。
すべて整数です
出力
1 行目に表示されるコメントの数、2 行目にコメントごとの行番号を n 個。
表示されなければ 0
例 1 · stdin
7
0 1 1 0 2 4 2
0 0 0 1 1 0 0
stdout
6
1 2 5 6 3 0 4
例 2 · stdin
5
0 1 2 3 0
0 1 0 0 0
stdout
3
1 2 0 0 3
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

ツリー表示は深さ優先探索(DFS)の訪問順そのものです:コメントを一つ出し、その返信を最後まで下りてから戻って次のコメントへ進みます。
一つ目の例の画面は 1, 2, 5, 7, 3, 4 の順で、4 番が折りたたまれているので 6 番は表示されません(5 番も折りたたまれていますが、返信がないので何も変わりません)。
再帰で書くのがいちばん短いですが、返信が一列に 10,000 回続くことがあります。
Python は再帰の上限が既定で 1,000 なので RecursionError で止まり、Craykon も数千段で止まります。
そこで八つの言語すべてでスタックを自分で使います:取り出したコメントに行番号を付け、折りたたまれていなければ返信をスタックに入れます。
落とし穴一:スタックは後に入れたものが先に出るので、返信を番号の大きいものから入れないと小さい番号が先に出ません。
番号順に入れると兄弟が逆に表示されます。
落とし穴二:スタックの代わりにキュー(前から取り出す)を使うと、層ごとに回る幅優先探索になり、返信が元のコメントのすぐ下ではなくずっと後に出ます。
落とし穴三:折りたたまれたコメントも自分の行は表示されます。
下りないだけです。
落とし穴四:折りたたまれたコメントに直接付いた返信だけを隠すと間違えます — 返信の返信も表示されません(二つ目の例の 4 番)。
下りなければ自然にそうなります。
スタックは言語ごとに違います:Python のリストの append・pop、JavaScript・TypeScript の配列の push・pop、Java の ArrayDeque の push・pop、C# の Stack<int>、C++ の std::stack(top() で読み、pop() で取り除く)、C はコメントごとに一度しか入らないので n + 1 個の配列と先頭の番号です。
Craykon はリストに 追加 で入れ、stack[stack.個数 - 1] を読んでから 位置で取り除く(stack.個数 - 1) で取り除きます。
C の解答は返信を連結リストの先頭に挿入するので、たどると大きい番号から出てきて、そのままスタックに入れれば済みます。

Pythonmain.py · 27 行
import sys data = sys.stdin.read().split()n = int(data[0])parent = [0] + [int(x) for x in data[1:1 + n]]          # parent[i] = i가 답한 댓글(0이면 글에 바로 단 댓글)folded = [0] + [int(x) for x in data[1 + n:1 + 2 * n]]  # folded[i] = 1이면 접힌 댓글 # replies[v] = v에 달린 답글들. 답글은 늘 번호가 더 크니 i를 차례로 넣으면 번호 순이 된다# replies[0]은 글에 바로 단 댓글들이다replies = [[] for _ in range(n + 1)]for i in range(1, n + 1):    replies[parent[i]].append(i) # 깊이 우선 탐색(DFS)을 스택으로: 꺼낸 차례가 곧 화면의 줄 차례다# 스택은 나중에 넣은 것이 먼저 나오니 번호가 큰 것부터 넣어야 작은 번호가 먼저 나온다line = [0] * (n + 1)shown = 0stack = replies[0][::-1]while stack:    v = stack.pop()    shown += 1    line[v] = shown    if not folded[v]:  # 접힌 댓글은 자기 줄만 보이고 답글로는 내려가지 않는다        stack.extend(reversed(replies[v])) print(shown)print(" ".join(map(str, line[1:])))
JavaScriptmain.js · 22 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];// replies[v] = v에 달린 답글들. 답글은 늘 번호가 더 크니 i를 차례로 넣으면 번호 순이 된다// replies[0]은 글에 바로 단 댓글들이다const replies = Array.from({ length: n + 1 }, () => []);for (let i = 1; i <= n; i++) replies[data[i]].push(i);const folded = (v) => data[n + v] === 1; // 접힌 댓글인가 // 깊이 우선 탐색(DFS)을 스택으로: 꺼낸 차례가 곧 화면의 줄 차례다// 스택은 나중에 넣은 것이 먼저 나오니 번호가 큰 것부터 넣어야 작은 번호가 먼저 나온다const line = new Array(n + 1).fill(0);let shown = 0;const stack = [...replies[0]].reverse();while (stack.length > 0) {  const v = stack.pop();  line[v] = ++shown;  if (folded(v)) continue; // 접힌 댓글은 자기 줄만 보이고 답글로는 내려가지 않는다  for (let k = replies[v].length - 1; k >= 0; k--) stack.push(replies[v][k]);} console.log(shown);console.log(line.slice(1).join(" "));
TypeScriptmain.ts · 24 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];// replies[v] = v에 달린 답글들. 답글은 늘 번호가 더 크니 i를 차례로 넣으면 번호 순이 된다// replies[0]은 글에 바로 단 댓글들이다const replies: number[][] = Array.from({ length: n + 1 }, (): number[] => []);for (let i = 1; i <= n; i++) replies[data[i]].push(i);const folded = (v: number): boolean => data[n + v] === 1; // 접힌 댓글인가 // 깊이 우선 탐색(DFS)을 스택으로: 꺼낸 차례가 곧 화면의 줄 차례다// 스택은 나중에 넣은 것이 먼저 나오니 번호가 큰 것부터 넣어야 작은 번호가 먼저 나온다const line: number[] = new Array<number>(n + 1).fill(0);let shown = 0;const stack: number[] = [...replies[0]].reverse();while (stack.length > 0) {  const v = stack.pop() as number;  line[v] = ++shown;  if (folded(v)) continue; // 접힌 댓글은 자기 줄만 보이고 답글로는 내려가지 않는다  for (let k = replies[v].length - 1; k >= 0; k--) stack.push(replies[v][k]);} console.log(shown);console.log(line.slice(1).join(" "));
JavaMain.java · 42 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));        st.nextToken();        int n = (int) st.nval;        // replies.get(v) = v에 달린 답글들. 답글은 늘 번호가 더 크니 i를 차례로 넣으면 번호 순이 된다        // replies.get(0)은 글에 바로 단 댓글들이다        List<List<Integer>> replies = new ArrayList<>();        for (int v = 0; v <= n; v++) replies.add(new ArrayList<>());        for (int i = 1; i <= n; i++) {            st.nextToken();            replies.get((int) st.nval).add(i);        }        boolean[] folded = new boolean[n + 1];        for (int i = 1; i <= n; i++) {            st.nextToken();            folded[i] = st.nval == 1;        }         // 깊이 우선 탐색(DFS)을 스택으로: 꺼낸 차례가 곧 화면의 줄 차례다        // 스택은 나중에 넣은 것이 먼저 나오니 번호가 큰 것부터 넣어야 작은 번호가 먼저 나온다        int[] line = new int[n + 1];        int shown = 0;        ArrayDeque<Integer> stack = new ArrayDeque<>();        stack.push(0); // 0번은 글 자체: 줄을 차지하지 않고 접히지도 않는다        while (!stack.isEmpty()) {            int v = stack.pop();            if (v != 0) line[v] = ++shown;            if (folded[v]) continue; // 접힌 댓글은 자기 줄만 보이고 답글로는 내려가지 않는다            List<Integer> r = replies.get(v);            for (int k = r.size() - 1; k >= 0; k--) stack.push(r.get(k));        }         StringBuilder sb = new StringBuilder();        sb.append(shown).append('\n');        for (int i = 1; i <= n; i++) sb.append(line[i]).append(i < n ? ' ' : '\n');        System.out.print(sb);    }}
C#Main.cs · 38 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(data[0]);        // replies[v] = v에 달린 답글들. 답글은 늘 번호가 더 크니 i를 차례로 넣으면 번호 순이 된다        // replies[0]은 글에 바로 단 댓글들이다        var replies = new List<int>[n + 1];        for (int v = 0; v <= n; v++) replies[v] = new List<int>();        for (int i = 1; i <= n; i++) replies[int.Parse(data[i])].Add(i);        var folded = new bool[n + 1];        for (int i = 1; i <= n; i++) folded[i] = data[n + i] == "1";         // 깊이 우선 탐색(DFS)을 스택으로: 꺼낸 차례가 곧 화면의 줄 차례다        // 스택은 나중에 넣은 것이 먼저 나오니 번호가 큰 것부터 넣어야 작은 번호가 먼저 나온다        var line = new int[n + 1];        int shown = 0;        var stack = new Stack<int>();        stack.Push(0); // 0번은 글 자체: 줄을 차지하지 않고 접히지도 않는다        while (stack.Count > 0)        {            int v = stack.Pop();            if (v != 0) line[v] = ++shown;            if (folded[v]) continue; // 접힌 댓글은 자기 줄만 보이고 답글로는 내려가지 않는다            for (int k = replies[v].Count - 1; k >= 0; k--) stack.Push(replies[v][k]);        }         var sb = new StringBuilder();        sb.Append(shown).Append('\n');        for (int i = 1; i <= n; i++) sb.Append(line[i]).Append(i < n ? ' ' : '\n');        Console.Write(sb);    }}
C++main.cpp · 39 行
#include <iostream>#include <stack>#include <vector>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n;    cin >> n;    // replies[v] = v에 달린 답글들. 답글은 늘 번호가 더 크니 i를 차례로 넣으면 번호 순이 된다    // replies[0]은 글에 바로 단 댓글들이다    vector<vector<int>> replies(n + 1);    for (int i = 1; i <= n; i++) {        int p;        cin >> p;        replies[p].push_back(i);    }    vector<int> folded(n + 1, 0);    for (int i = 1; i <= n; i++) cin >> folded[i];     // 깊이 우선 탐색(DFS)을 스택으로: 꺼낸 차례가 곧 화면의 줄 차례다    // 스택은 나중에 넣은 것이 먼저 나오니 번호가 큰 것부터 넣어야 작은 번호가 먼저 나온다    vector<int> line(n + 1, 0);    int shown = 0;    stack<int> st;    st.push(0); // 0번은 글 자체: 줄을 차지하지 않고 접히지도 않는다    while (!st.empty()) {        int v = st.top();        st.pop();        if (v != 0) line[v] = ++shown;        if (folded[v]) continue; // 접힌 댓글은 자기 줄만 보이고 답글로는 내려가지 않는다        for (auto it = replies[v].rbegin(); it != replies[v].rend(); ++it) st.push(*it);    }     cout << shown << '\n';    for (int i = 1; i <= n; i++) cout << line[i] << (i < n ? ' ' : '\n');    return 0;}
Cmain.c · 38 行
#include <stdio.h>#include <stdlib.h> int main(void) {    int n;    if (scanf("%d", &n) != 1) return 0;    // 답글 목록을 배열 둘로: first[v]에서 시작해 next[i]로 따라간다(v = 0은 글 자체)    // 새 답글을 목록의 맨 앞에 끼우므로, 따라가면 번호가 큰 답글부터 나온다    int *first = calloc(n + 1, sizeof(int));    int *next = calloc(n + 1, sizeof(int));    int *folded = calloc(n + 1, sizeof(int));    for (int i = 1; i <= n; i++) {        int p;        scanf("%d", &p);        next[i] = first[p];        first[p] = i;    }    for (int i = 1; i <= n; i++) scanf("%d", &folded[i]);     // 깊이 우선 탐색(DFS)을 스택으로: 꺼낸 차례가 곧 화면의 줄 차례다    // 스택은 나중에 넣은 것이 먼저 나오니 번호가 큰 것부터 넣어야 작은 번호가 먼저 나온다    int *line = calloc(n + 1, sizeof(int));    int *stack = malloc((n + 1) * sizeof(int)); // 댓글마다 한 번만 들어간다    int top = 0, shown = 0;    stack[top++] = 0; // 0번은 글 자체: 줄을 차지하지 않고 접히지도 않는다    while (top > 0) {        int v = stack[--top];        if (v != 0) line[v] = ++shown;        if (folded[v]) continue; // 접힌 댓글은 자기 줄만 보이고 답글로는 내려가지 않는다        for (int r = first[v]; r != 0; r = next[r]) stack[top++] = r;    }     printf("%d\n", shown);    for (int i = 1; i <= n; i++) printf(i > 1 ? " %d" : "%d", line[i]);    printf("\n");    free(first); free(next); free(folded); free(line); free(stack);    return 0;}
Craykonmain.cray · 51 行
n = コンソール.数読み()[0];parent = コンソール.数読み(); // parent[i - 1] = i번 댓글이 답한 댓글(0이면 글에 바로 단 댓글)folded = コンソール.数読み(); // folded[i - 1] = 1이면 접힌 댓글 // 목록은 10,000칸까지라 댓글 번호에서 1을 빼 0번부터 둔다// replies[v] = v에 달린 답글들. 답글은 늘 번호가 더 크니 차례로 넣으면 번호 순이 된다replies = [];繰り返す (v = 0; v < n; v++){    replies.追加([]);}tops = []; // 글에 바로 단 댓글들繰り返す (i = 0; i < n; i++){    もし (parent[i] == 0)    {        tops.追加(i);    }    でなければ    {        replies[parent[i] - 1].追加(i);    }} // 깊이 우선 탐색(DFS)을 스택으로: 꺼낸 차례가 곧 화면의 줄 차례다// 스택은 나중에 넣은 것이 먼저 나오니 번호가 큰 것부터 넣어야 작은 번호가 먼저 나온다line = 新リスト(n, 0);shown = 0;stack = [];繰り返す (k = tops.個数 - 1; k >= 0; k--){    stack.追加(tops[k]);}間 (stack.個数 > 0){    v = stack[stack.個数 - 1];    stack.位置で取り除く(stack.個数 - 1);    shown += 1;    line[v] = shown;    // 접힌 댓글은 자기 줄만 보이고 답글로는 내려가지 않는다    もし (folded[v] == 0)    {        繰り返す (k = replies[v].個数 - 1; k >= 0; k--)        {            stack.追加(replies[v][k]);        }    }} コンソール.書く(shown);コンソール.書く(line);