{ }codelper
깊이 우선 탐색스택P0292

접힌 댓글 타래의 줄 번호

게시판의 글 하나에 댓글 n개가 달려 있고, 쓴 차례대로 1번부터 n번까지 번호가 붙어 있다. 댓글은 글에 바로 달거나, 먼저 쓴 다른 댓글에 답글로 단다. i번 댓글에 대해 pᵢ가 주어진다.
pᵢ가 0이면 글에 바로 단 댓글이고, 아니면 pᵢ번 댓글에 단 답글이다.
답글은 언제나 번호가 더 작은 댓글에 단다. 화면에는 댓글을 한 줄에 하나씩 다음 차례로 보인다. 글에 바로 단 댓글을 번호가 작은 것부터 보인다. 댓글 하나를 보인 다음에는, 그 댓글에 달린 답글을 번호가 작은 것부터 바로 밑에 보인다. 답글 하나를 보일 때도 같은 규칙으로 그 답글에 달린 답글까지 모두 보인 다음에 다음 답글로 넘어간다. 댓글마다 접혔는지가 주어진다.
cᵢ가 1이면 i번 댓글은 접힌 댓글이다. 접힌 댓글은 자기 한 줄은 보이지만, 그 댓글에 달린 답글과 그 답글에 달린 답글들은 하나도 보이지 않는다. 보이는 댓글에는 화면 위에서부터 1, 2, 3, …으로 줄 번호가 붙는다.
보이지 않는 댓글은 줄을 차지하지 않는다. 첫 줄에 화면에 보이는 댓글의 수를 출력하라. 둘째 줄에 1번부터 n번 댓글까지 줄 번호를 차례로 빈칸 하나로 띄워 출력하라.
보이지 않는 댓글은 0으로 출력한다.

입력
첫 줄에 n, 둘째 줄에 p₁ … pₙ, 셋째 줄에 c₁ … cₙ이 빈칸 하나로 나뉘어 주어진다.
1 ≤ n ≤ 10,000, 0 ≤ pᵢ < i, cᵢ는 0 또는 1.
모두 정수다
출력
첫 줄에 보이는 댓글의 수, 둘째 줄에 댓글마다 줄 번호 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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

타래 화면은 깊이 우선 탐색(DFS)의 방문 차례 그대로예요: 댓글 하나를 찍고, 그 답글들을 끝까지 내려갔다가 돌아와 다음 댓글로 가요.
첫 예시의 화면은 1, 2, 5, 7, 3, 4 차례이고, 4번이 접혀서 6번은 안 보여요(5번은 접혀 있어도 답글이 없으니 달라지는 것이 없어요).
재귀로 쓰면 가장 짧지만 답글이 한 줄로 10,000번 이어질 수 있어요.
Python은 재귀 한도가 기본 1,000이라 RecursionError로 멈추고, 크레이콘도 몇천 단계에서 멈춰요.
그래서 여덟 언어 모두 스택을 직접 써요: 꺼낸 댓글에 줄 번호를 주고, 접히지 않았으면 답글들을 스택에 넣어요.
함정 하나: 스택은 나중에 넣은 것이 먼저 나오니 답글을 번호가 큰 것부터 넣어야 작은 번호가 먼저 나와요.
번호 순으로 넣으면 형제끼리 거꾸로 보여요.
함정 둘: 스택 대신 큐(앞에서 꺼내기)를 쓰면 층별로 도는 너비 우선 탐색이 되어, 답글이 원래 댓글 바로 밑이 아니라 한참 뒤에 나와요.
함정 셋: 접힌 댓글도 자기 줄은 보여요.
내려가지만 않아요.
함정 넷: 접힌 댓글에 바로 달린 답글만 숨기면 틀려요 — 답글의 답글도 안 보여요(둘째 예시의 4번).
내려가지 않으면 저절로 그렇게 돼요.
스택은 언어마다 달라요: Python 리스트의 append·pop, JavaScript·TypeScript 배열의 push·pop, Java ArrayDeque의 push·pop, C# Stack<int>, C++ std::stack(꺼낼 때 top()으로 읽고 pop()으로 뺀다), C는 댓글마다 한 번만 들어가니 n + 1칸 배열과 꼭대기 번호예요.
크레이콘은 목록에 넣기로 넣고 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);