java 洛谷题单【数据结构1-2】二叉树

P4715 【深基16.例1】淘汰赛

解题思路

  • 半区分配:将前半部分国家分配到左半区,后半部分国家分配到右半区,分别找到两个半区的最强国家。
  • 决赛和亚军确定:最后比较两个半区最强国家的能力值,失败者即为亚军,输出其编号。
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // 读入n
        int n = input.nextInt();
        int numTeams = (int) Math.pow(2, n);  // 参赛队伍的数量为 2^n

        // 读入每个国家的能力值
        int[] abilities = new int[numTeams + 1];  // 1-based indexing
        for (int i = 1; i <= numTeams; i++) {
            abilities[i] = input.nextInt();
        }

        // 找出左半区和右半区的最强者
        int leftBest = 1;  // 初始假设左半区第一个国家是最强的
        int rightBest = numTeams / 2 + 1;  // 初始假设右半区第一个国家是最强的

        // 找左半区最强的国家
        for (int i = 2; i <= numTeams / 2; i++) {
            if (abilities[i] > abilities[leftBest]) {
                leftBest = i;
            }
        }

        // 找右半区最强的国家
        for (int i = numTeams / 2 + 2; i <= numTeams; i++) {
            if (abilities[i] > abilities[rightBest]) {
                rightBest = i;
            }
        }

        // 决赛:leftBest 和 rightBest 进行比赛,输者即为亚军
        if (abilities[leftBest] > abilities[rightBest]) {
            System.out.println(rightBest);
        } else {
            System.out.println(leftBest);
        }
    }
}

P4913 【深基16.例3】二叉树深度 

1. 数据结构设计

  • 使用一个数组或列表来表示树结构。每个节点的左右子节点可以用 Node 类或结构体来表示,存储其左、右子节点的编号。
  • 数组的大小为 n+1(节点编号从 1 开始)。

2. 深度计算方法

  • 递归方法

    • 从根节点开始,定义一个递归函数 dfs,计算当前节点的深度。
    • 当节点为 0 时返回深度为 0(表示到达叶子节点)。
    • 递归调用左右子节点,更新最大深度。
  • 迭代方法

    • 使用栈来模拟递归。初始化栈,存储每个节点及其当前深度。
    • 弹出栈顶元素,更新最大深度,然后将左右子节点及其深度推入栈中。

3. 输入输出处理

  • 读取节点数 n,并在循环中读取每个节点的左右子节点信息。
  • 调用深度计算函数,并输出计算得到的最大深度。

4. 边界情况考虑

  • 处理节点数为 0 或 1 的情况。
  • 确保节点的左右子节点编号有效,避免访问越界。
import java.util.Scanner;
import java.util.Stack;

public class Main {
    static class Node {
        int left, right; // 左右子节点
    }

    static Node[] tree; // 存储树的结构
    static int n;

    // 迭代方式计算树的深度
    static int calculateDepth() {
        Stack<int[]> stack = new Stack<>();
        stack.push(new int[]{1, 1}); // {节点编号, 当前深度}
        int maxDepth = 0;

        while (!stack.isEmpty()) {
            int[] current = stack.pop();
            int id = current[0];
            int deep = current[1];

            if (id == 0) continue; // 如果是叶子节点,跳过

            maxDepth = Math.max(maxDepth, deep); // 更新最大深度
            stack.push(new int[]{tree[id].left, deep + 1}); // 左子节点
            stack.push(new int[]{tree[id].right, deep + 1}); // 右子节点
        }

        return maxDepth;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        n = input.nextInt(); // 读取节点数
        tree = new Node[n + 1]; // 初始化树的数组

        // 读取节点的左、右子节点
        for (int i = 1; i <= n; i++) {
            tree[i] = new Node(); // 初始化每个节点
            tree[i].left = input.nextInt();
            tree[i].right = input.nextInt();
        }

        int depth = calculateDepth(); // 计算深度
        System.out.println(depth); // 输出深度
    }
}

P1827 [USACO3.4] 美国血统 American Heritage

解题思路

  • 理解遍历方式

    • 前序遍历:第一个字符是根节点。
    • 中序遍历:根节点将树分为左子树和右子树。
  • 构建树的后序遍历

    • 从前序遍历中找到根节点。
    • 在中序遍历中找到根节点的位置,并据此分割左右子树。
    • 递归处理左子树和右子树,最后合并后序遍历结果。
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    static String inorder, preorder;
    static Map<Character, Integer> inorderIndexMap = new HashMap<>();

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        inorder = input.nextLine().trim();
        preorder = input.nextLine().trim();

        // 构建中序遍历字符的索引
        for (int i = 0; i < inorder.length(); i++) {
            inorderIndexMap.put(inorder.charAt(i), i);
        }

        // 获取后序遍历
        String postorder = getPostOrder(0, preorder.length() - 1, 0, inorder.length() - 1);
        System.out.println(postorder);
    }

    private static String getPostOrder(int preStart, int preEnd, int inStart, int inEnd) {
        if (preStart > preEnd || inStart > inEnd) {
            return "";
        }

        // 前序遍历的根节点
        char root = preorder.charAt(preStart);
        // 找到根节点在中序遍历中的索引
        int rootIndexInInorder = inorderIndexMap.get(root);
        // 左子树的节点数量
        int leftSubtreeSize = rootIndexInInorder - inStart;

        // 递归处理左子树和右子树
        String leftPostOrder = getPostOrder(preStart + 1, preStart + leftSubtreeSize, inStart, rootIndexInInorder - 1);
        String rightPostOrder = getPostOrder(preStart + leftSubtreeSize + 1, preEnd, rootIndexInInorder + 1, inEnd);

        // 后序遍历是左子树 + 右子树 + 根节点
        return leftPostOrder + rightPostOrder + root;
    }
}

P5076 【深基16.例7】普通二叉树(简化版)

解题思路

数据结构:使用二叉树

操作分析

  1. 查询数 x 的排名(操作1):

    • 使用 TreeSetheadSet(x) 方法可以获得所有小于 xxx 的元素集合。
    • 计算其大小并加1(因为排名从1开始)。
  2. 查询排名为 x 的数(操作2):

    • TreeSet 转换为数组或列表,直接通过索引访问。
    • 注意索引需要减1,因为排名从1开始而数组索引从0开始。
  3. 求前驱(操作3):

    • 使用 TreeSetlower(x) 方法可以直接获取小于 xxx 的最大值。
    • 如果不存在前驱,则返回特定的值 −2147483647。
  4. 求后继(操作4):

    • 使用 higher(x) 方法获取大于 xxx 的最小值。
    • 如果不存在后继,则返回特定的值 2147483647。
  5. 插入数 x(操作5):

    • 直接调用 add(x) 方法将 x 插入到集合中。根据题意保证插入前 x 不在集合里。
import java.util.*;

public class Main {
    private static TreeSet<Integer> set;

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int q = input.nextInt();
        set = new TreeSet<>();

        StringBuilder output = new StringBuilder();

        for (int i = 0; i < q; i++) {
            int op = input.nextInt();
            int x = input.nextInt();

            switch (op) {
                case 1: // 查询排名
                    int rank = getRank(x);
                    output.append(rank).append("\n");
                    break;
                case 2: // 查询排名为x的数
                    int value = getValueByRank(x);
                    output.append(value).append("\n");
                    break;
                case 3: // 求前驱
                    int predecessor = getPredecessor(x);
                    output.append(predecessor).append("\n");
                    break;
                case 4: // 求后继
                    int successor = getSuccessor(x);
                    output.append(successor).append("\n");
                    break;
                case 5: // 插入数x
                    set.add(x);
                    break;
                default:
                    break;
            }
        }

        System.out.print(output);
    }

    private static int getRank(int x) {
        // 计算小于x的元素个数
        return set.headSet(x).size() + 1; // +1因为排名从1开始
    }

    private static int getValueByRank(int rank) {
        // 获取排名为rank的数
        return (Integer) set.toArray()[rank - 1]; // rank-1因为数组索引从0开始
    }

    private static int getPredecessor(int x) {
        // 获取前驱
        Integer pred = set.lower(x);
        return (pred != null) ? pred : -2147483647; // 若不存在前驱返回 -2147483647
    }

    private static int getSuccessor(int x) {
        // 获取后继
        Integer succ = set.higher(x);
        return (succ != null) ? succ : 2147483647; // 若不存在后继返回 2147483647
    }
}

P1364 医院设置

解题思路

  • 树的表示与输入解析:通过输入的描述可以构造二叉树,每个结点包含了人口数和它的左、右子树链接。

  • 距离和的计算:可以通过深度优先搜索(DFS)来计算每个结点作为医院时的总距离和。首先从任意一个结点开始递归计算其所有子结点与它的距离和,再使用动态规划的思想通过子问题解决整体问题。

  • 自底向上计算距离和:通过一次DFS计算出以任意一个结点为根时的距离和,然后通过递归遍历整棵树的其他结点,通过子树距离和转移来快速更新。

import java.util.*;

class TreeNode {
    int population; // 当前结点的人口数
    int left;       // 左子结点的索引
    int right;      // 右子结点的索引

    public TreeNode(int population, int left, int right) {
        this.population = population;
        this.left = left;
        this.right = right;
    }
}

public class Main {
    static TreeNode[] tree; // 用数组来表示树
    static int[] subtreePopulation; // 记录以当前结点为根的子树总人口数
    static int[] subtreeDistanceSum; // 记录以当前结点为根的子树距离和
    static int n; // 结点数

    // 计算以当前结点为根的子树人口和以及距离和
    static void dfs1(int node) {
        if (node == 0) return;
        int left = tree[node].left;
        int right = tree[node].right;

        dfs1(left);  // 递归计算左子树
        dfs1(right); // 递归计算右子树

        // 计算以当前结点为根的子树总人口和
        subtreePopulation[node] = tree[node].population;
        if (left != 0) subtreePopulation[node] += subtreePopulation[left];
        if (right != 0) subtreePopulation[node] += subtreePopulation[right];

        // 计算以当前结点为根的子树距离和
        if (left != 0) subtreeDistanceSum[node] += subtreeDistanceSum[left] + subtreePopulation[left];
        if (right != 0) subtreeDistanceSum[node] += subtreeDistanceSum[right] + subtreePopulation[right];
    }

    // 递归从根结点开始转移
    static void dfs2(int node, int parentDistanceSum) {
        if (node == 0) return;

        // 当前结点的总距离和 = 父结点的距离和 + 父结点距离和 - 左右子树的影响
        subtreeDistanceSum[node] = parentDistanceSum;
        int left = tree[node].left;
        int right = tree[node].right;

        if (left != 0) dfs2(left, subtreeDistanceSum[node] - subtreePopulation[left] + (subtreePopulation[1] - subtreePopulation[left]));
        if (right != 0) dfs2(right, subtreeDistanceSum[node] - subtreePopulation[right] + (subtreePopulation[1] - subtreePopulation[right]));
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        n = input.nextInt();
        tree = new TreeNode[n + 1];
        subtreePopulation = new int[n + 1];
        subtreeDistanceSum = new int[n + 1];

        for (int i = 1; i <= n; i++) {
            int population = input.nextInt();
            int left = input.nextInt();
            int right = input.nextInt();
            tree[i] = new TreeNode(population, left, right);
        }

        // 第一次 DFS:计算以 1 为根的子树人口和与距离和
        dfs1(1);

        // 第二次 DFS:动态转移,从根结点向其他结点转移
        dfs2(1, subtreeDistanceSum[1]);

        // 寻找最小的距离和
        int minDistanceSum = Integer.MAX_VALUE;
        for (int i = 1; i <= n; i++) {
            minDistanceSum = Math.min(minDistanceSum, subtreeDistanceSum[i]);
        }

        System.out.println(minDistanceSum);
    }
}

P1229 遍历问题

解题思路

  • 根节点不参与子树划分:前序遍历的第一个字符和后序遍历的最后一个字符是根节点,根节点不会影响左右子树的划分。代码通过遍历前序和后序字符串中间部分来寻找可能的左右子树划分。

  • 寻找左右子树的模糊性:通过找到前序和后序遍历中成对的匹配字符(前序中某个字符的后续和后序中对应字符的前驱),代码识别出左右子树划分的模糊性。每一对匹配字符可以代表一个子树划分点。

  • 中序遍历组合数的计算:如果有 ans 对匹配子树,每一对子树可以有两种排列方式(左右子树顺序不同),所以总的中序遍历组合数是 2^ans

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        String str1 = input.next();
        String str2 = input.next();

        int ans = 0;

        for (int i = 0; i < str1.length(); i++) {
            for (int j = 1; j < str2.length(); j++) {
                if (str1.charAt(i) == str2.charAt(j) && (i + 1 < str1.length() && str1.charAt(i + 1) == str2.charAt(j - 1))) {
                    ans++;
                }
            }
        }

        System.out.println(1 << ans);
    }
}

P1305 新二叉树

解题思路

  • 二叉树节点类设计 (Node 类)

    • 每个节点用一个字符表示,并且每个节点可以有左子树和右子树。
    • Node 类包含三个属性:节点的值 val、左子节点 left 和右子节点 right,初始时左右子节点都为 null
  • 读取输入

    • 首先读取一个整数 n,表示树中节点的数量。题目保证节点数不超过 26,所以可以直接按顺序读取每个节点的信息。
    • 接下来读取每个节点的关系,依次表示父节点、左子节点、右子节点。空节点使用 '*' 表示。
    • 输入的第一行一定是根节点的定义,后续的行则表示其他节点及其左右子节点。
  • 构建二叉树

    • 使用一个哈希表 (Map<Character, Node>) 来存储每个节点,其中键是节点的字符,值是该节点的 Node 对象。
    • 遍历输入的每行信息,根据当前行的第一个字符创建父节点,并根据后两个字符判断是否有左子节点和右子节点,分别连接父节点和其子节点。如果子节点为 '*',表示该节点为空,不做处理。
  • 前序遍历

    • 使用递归方法进行前序遍历。前序遍历的顺序是:
      • 先访问根节点(直接输出根节点的值)。
      • 递归访问左子节点。
      • 递归访问右子节点。
    • 递归函数 preOrder(Node root) 对每个节点进行处理,如果当前节点为 null,则返回。如果不为 null,先打印当前节点的值,再递归处理左子树和右子树。
  • 根节点的获取

    • 根节点是第一行读取的节点,因此在第一次读取时,将其保存为 rootNode 变量。在所有节点构建完成后,直接从哈希表中获取该根节点并进行前序遍历。
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

class Node {
    char val;
    Node left;
    Node right;

    public Node(char val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class Main {
    static Map<Character, Node> tree = new HashMap<>();

    // 前序遍历
    public static void preOrder(Node root) {
        if (root == null) return;
        System.out.print(root.val);  // 先输出根节点
        preOrder(root.left);  // 递归遍历左子树
        preOrder(root.right);  // 递归遍历右子树
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();  // 读取节点数
        input.nextLine();  // 读取下一行

        // 根节点的字符
        char rootNode = ' ';

        // 读入每个节点和其左右子节点的关系
        for (int i = 0; i < n; i++) {
            String line = input.nextLine();
            char parent = line.charAt(0);
            char leftChild = line.charAt(1);
            char rightChild = line.charAt(2);

            // 记录第一个输入的节点为根节点
            if (i == 0) {
                rootNode = parent;
            }

            // 如果父节点还不存在,创建一个新的节点
            tree.putIfAbsent(parent, new Node(parent));

            // 如果左子节点不是'*',创建并连接
            if (leftChild != '*') {
                tree.putIfAbsent(leftChild, new Node(leftChild));
                tree.get(parent).left = tree.get(leftChild);
            }

            // 如果右子节点不是'*',创建并连接
            if (rightChild != '*') {
                tree.putIfAbsent(rightChild, new Node(rightChild));
                tree.get(parent).right = tree.get(rightChild);
            }
        }

        // 根节点就是第一个输入的节点
        Node root = tree.get(rootNode);

        // 前序遍历
        preOrder(root);
    }
}

P1030 [NOIP2001 普及组] 求先序排列

解题思路

  • 后序遍历的特点

    • 后序遍历是“左子树 - 右子树 - 根节点”。
    • 因此后序遍历的最后一个节点一定是二叉树的根节点。
  • 中序遍历的特点

    • 中序遍历是“左子树 - 根节点 - 右子树”。
    • 因此根据根节点,可以将中序遍历分为两部分,根节点左边是左子树,右边是右子树。
  • 递归构造树

    • 通过后序遍历找到根节点。
    • 根据根节点在中序遍历中的位置,将中序遍历划分为左子树和右子树部分。
    • 对左子树和右子树分别递归进行相同的操作。
    • 最后,按照“根节点 - 左子树 - 右子树”的顺序输出先序遍历结果。
import java.util.Scanner;

public class Main {

    // 递归构造先序遍历
    public static void findPreOrder(String inOrder, String postOrder) {
        if (postOrder.isEmpty()) {
            return;
        }

        // 后序遍历的最后一个字符是根节点
        char root = postOrder.charAt(postOrder.length() - 1);

        // 输出根节点
        System.out.print(root);

        // 在中序遍历中找到根节点的位置
        int rootIndex = inOrder.indexOf(root);

        // 划分中序遍历为左子树和右子树
        String inOrderLeft = inOrder.substring(0, rootIndex);
        String inOrderRight = inOrder.substring(rootIndex + 1);

        // 划分后序遍历为左子树和右子树的后序遍历
        String postOrderLeft = postOrder.substring(0, inOrderLeft.length());
        String postOrderRight = postOrder.substring(inOrderLeft.length(), postOrder.length() - 1);

        // 递归处理左子树
        findPreOrder(inOrderLeft, postOrderLeft);

        // 递归处理右子树
        findPreOrder(inOrderRight, postOrderRight);
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // 输入中序遍历和后序遍历
        String inOrder = input.nextLine();
        String postOrder = input.nextLine();

        // 求先序遍历并输出
        findPreOrder(inOrder, postOrder);
    }
}

P3884 [JLOI2009] 二叉树问题

解题思路

  • 1. 邻接表存储树的结构
  • 树的结构通过边描述,每条边连接两个节点。
  • 使用邻接表(head[]edge[])存储边的信息,以有效管理和访问节点的相邻节点。
  • 2. SPFA 算法求最短路径
  • SPFA(Shortest Path Faster Algorithm) 是一种基于队列的单源最短路径算法,适合稀疏图(如树结构)中的最短路径计算。

  • 代码通过 SPFA 来计算从某个起点(如节点 1)到所有其他节点的最短路径,得到从根节点到每个节点的距离(也即树的深度)。

    SPFA 的过程:

    • 初始化所有节点的距离为无穷大,起点的距离为 0。
    • 利用队列进行松弛操作,更新每个节点到起点的最短路径。
    • 如果某个节点的距离被更新,且该节点不在队列中,则将其加入队列。
  • 3. 计算树的最大深度
  • 通过 SPFA 计算出从根节点(即节点 1)到所有其他节点的最短路径,存储在 dis[] 数组中。
  • 遍历 dis[] 数组,找出其中的最大值,即为树的最大深度。
  • 4. 计算树的最大宽度
  • 在遍历 dis[] 数组的过程中,记录每个不同深度(层次)上节点的数量,存储在 box[] 数组中。
  • 找出 box[] 数组的最大值,即为树的最大宽度。
  • 5. 计算两个指定节点之间的最短路径
  • 在输入两个指定的节点 uv 后,使用 SPFA 从节点 u 出发,计算到所有其他节点的最短路径。
  • 通过 dis[v] 获得从节点 u 到节点 v 的最短距离。
import java.util.*;

class Node {
    int to, next, value;
    // 构造函数,用于初始化节点信息,包括目的节点、下一个边的索引以及边的权值
    Node(int to, int next, int value) {
        this.to = to;
        this.next = next;
        this.value = value;
    }
}

public class Main {
    static final int MAXN = 1000; // 最大节点数
    static int[] dis = new int[MAXN + 1], vis = new int[MAXN + 1], head = new int[MAXN + 1], box = new int[MAXN + 1];
    static Node[] edge = new Node[2 * MAXN]; // 用于存储所有边的邻接表
    static int tot = 0; // 边的计数器

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt(); // 输入节点数量
        // 读取树的边,并建立邻接表
        for (int i = 1; i < n; i++) {
            int u = input.nextInt(), v = input.nextInt(); // 输入每条边的两个节点
            addedge(u, v, 1); // 从父节点到子节点的边权为 1
            addedge(v, u, 2); // 从子节点到父节点的边权为 2
        }
        int u = input.nextInt(), v = input.nextInt(); // 输入需要查询路径的两个节点
        SPFA(1); // 从根节点 1 开始,使用 SPFA 算法计算最短路径

        int ans = 0;
        // 遍历每个节点,统计距离根节点的最远距离(即最大深度)
        for (int i = 1; i <= n; i++) {
            box[dis[i]]++; // 统计每层的节点数量
            ans = Math.max(ans, dis[i]); // 计算最大深度
        }
        System.out.println(ans + 1); // 输出树的最大深度(加上根节点)

        ans = 0;
        // 找到层中最多节点的层数(即最大宽度)
        for (int i = 1; i <= n; i++) {
            ans = Math.max(ans, box[i]); // 计算最大宽度
        }
        System.out.println(ans); // 输出最大宽度

        // 重新计算从节点 u 到节点 v 的最短路径
        SPFA(u);
        System.out.println(dis[v]); // 输出节点 u 到节点 v 的最短距离
    }

    // 添加一条边到邻接表
    static void addedge(int x, int y, int w) {
        edge[++tot] = new Node(y, head[x], w); // 创建新边
        head[x] = tot; // 更新当前节点的头部指针
    }

    // SPFA 算法用于计算单源最短路径
    static void SPFA(int s) {
        Queue<Integer> q = new LinkedList<>(); // 队列用于存储待处理的节点
        Arrays.fill(dis, Integer.MAX_VALUE); // 初始化所有节点的距离为无穷大
        Arrays.fill(vis, 0); // 初始化访问标记为 0,表示节点未在队列中
        dis[s] = 0; // 起点到自身的距离为 0
        vis[s] = 1; // 将起点标记为已访问
        q.add(s); // 将起点加入队列

        // 开始松弛操作,更新节点的最短距离
        while (!q.isEmpty()) {
            int x = q.poll(); // 从队列中取出一个节点
            vis[x] = 0; // 标记该节点不在队列中
            // 遍历从节点 x 出发的所有边
            for (int i = head[x]; i != 0; i = edge[i].next) {
                // 如果通过 x 可以使某个相邻节点的距离更短,则进行更新
                if (dis[edge[i].to] > dis[x] + edge[i].value) {
                    dis[edge[i].to] = dis[x] + edge[i].value; // 更新相邻节点的最短距离
                    // 如果该相邻节点不在队列中,则将其加入队列
                    if (vis[edge[i].to] == 0) {
                        vis[edge[i].to] = 1; // 标记相邻节点已在队列中
                        q.add(edge[i].to); // 将该节点加入队列
                    }
                }
            }
        }
    }
}

P1185 绘制二叉树

解题思路

  • 输入处理

    • 使用 BufferedReader 读取输入,利用 StringTokenizer 分割输入数据,以获得树的深度 k 和不可绘制的节点数 p
    • 通过循环读取每个不可绘制的节点,并将其记录在布尔数组 f 中。
  • 树形结构绘制

    • 根据树的深度 k,计算节点的总数 n 和画布的宽度 m
    • 使用深度优先搜索(DFS)绘制树的节点和边。函数 dfs1 中根据当前节点的位置、父节点信息以及深度信息决定如何绘制节点和边。
    • 如果绘制到达叶子节点(深度为 n),则在相应的位置绘制叶子节点。
  • 输出结果

    • 将整个画布以字符的形式输出到控制台,使用 BufferedWriter 提高输出效率。
    • 每行绘制完成后调用 newLine() 输出换行。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.StringTokenizer;

public class Main {
    static int k, n, m, p, x, y; // k: 树的深度,n: 节点数,m: 画布宽度,p: 不可绘制的节点数
    static char[][] c = new char[800][1600]; // 绘制树的字符数组
    static boolean[][] f = new boolean[800][1600]; // 记录不可绘制的节点
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 输入流
    static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); // 输出流
    static StringTokenizer st; // 用于分割输入字符串

    // 深度优先搜索,绘制树形结构
    static void dfs1(int x, int y, int a, int b, int k, int xx, int yy) {
        // 如果到达树的最底层,绘制叶子节点
        if (x == n) {
            c[x][y] = 'o';
            return;
        }
        // 根据当前的 k 值决定绘制点还是边
        if (k == 1) {
            c[x][y] = 'o'; // 绘制节点
            int X = xx + 1, Y = (yy - 1) * 2 + 1; // 左儿子的位置
            // 如果左儿子不可绘制,则继续绘制
            if (!f[X][Y]) dfs1(x + 1, y - 1, a + 1, b, 2, X, Y);
            X = xx + 1;
            Y = yy * 2; // 右儿子的位置
            // 如果右儿子不可绘制,则继续绘制
            if (!f[X][Y]) dfs1(x + 1, y + 1, a + 1, b, 3, X, Y);
        } else if (k == 2) {
            c[x][y] = '/'; // 绘制左边的边
            // 判断接下来绘制点还是边
            if (a * 2 == b) dfs1(x + 1, y - 1, 1, a, 1, xx, yy);
            else dfs1(x + 1, y - 1, a + 1, b, 2, xx, yy);
        } else if (k == 3) {
            c[x][y] = '\\'; // 绘制右边的边
            // 判断接下来绘制点还是边
            if (a * 2 == b) dfs1(x + 1, y + 1, 1, a, 1, xx, yy);
            else dfs1(x + 1, y + 1, a + 1, b, 3, xx, yy);
        }
    }

    // 计算树的大小和绘制树形结构
    static void make(int k) {
        n = 3; // 初始设置为 3 层
        for (int i = 3; i <= k; i++) n *= 2; // 计算层数
        m = 6 * (1 << (k - 2)) - 1; // 计算画布的宽度
        // 初始化画布,填充空格
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++) c[i][j] = ' ';
        dfs1(1, m / 2 + 1, 1, n, 1, 1, 1); // 从根节点开始绘制
    }

    public static void main(String[] args) throws IOException {
        // 读取输入的树的深度和不可绘制节点数
        st = new StringTokenizer(br.readLine());
        k = Integer.parseInt(st.nextToken());
        p = Integer.parseInt(st.nextToken());

        // 读取不可绘制的节点
        while (p-- > 0) {
            st = new StringTokenizer(br.readLine());
            x = Integer.parseInt(st.nextToken());
            y = Integer.parseInt(st.nextToken());
            f[x][y] = true; // 标记这个节点为不可绘制
        }

        // 特殊情况处理
        if (k == 1) {
            n = m = 1;
            c[1][1] = 'o'; // 仅绘制一个节点
        } else {
            make(k); // 计算并绘制树
        }

        // 使用 BufferedWriter 输出结果
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                bw.write(c[i][j]); // 输出画布的每个字符
            }
            bw.newLine(); // 换行
        }

        bw.flush(); // 刷新输出流,确保所有数据都被写出
        bw.close(); // 关闭输出流
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HeShen.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值