首页 > 技术文章 > 2017年蓝桥杯国赛Java组——合根植物 只得了一半分 99%是时间超时 后面会优化(并查集)

pengsay 2021-03-19 20:18 原文

题目:

问题描述
w星球的一个种植园,被分成 m * n 个小格子(东西方向m行,南北方向n列)。每个格子里种了一株合根植物。
这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。
如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?


输入格式
第一行,两个整数m,n,用空格分开,表示格子的行数、列数(1<m,n<1000)。
接下来一行,一个整数k,表示下面还有k行数据(0<k<100000)
接下来k行,第行两个整数a,b,表示编号为a的小格子和编号为b的小格子合根了。
格子的编号一行一行,从上到下,从左到右编号。


比如:5 * 4 的小格子,编号:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20


样例输入
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17
样例输出
5
样例说明
  其合根情况参考下图

 

 

分析:

一开始我看到这是考察并查集的题目,就直接往这方面想,因为这题也是解决合并与查询的问题,所以用并查集是肯定的,但是要用哪种数据结构来表示所有集合呢,这里我构造了二叉排序树用来存储每个集合的所有元素,然后针对每次输入的两个数之间的关系,将这两个数所在的集合合并,不过要先判断这两个数是否在一个集合中,如果在就不用操作,反之则进行上面所说的操作,每次合并完之后,就要将另一个树中的所有元素对应在dp数组中的值修改为合并的那棵树对应在dp数组中的值。即dp[b] = dp[a],等接收完所有可能合根的情况之后,就计算一下arr数组中还剩下哪些不为null的集合,将统计之后的结果输出即可。这个算法经蓝桥官网的OJ测试之后,最后得了60来分,我分析原因,应该是我的二叉树在查询与合并的操作上效率会有点低。

 

上代码:

  1 package com.lzp.lanqiaocontests;
  2 
  3 import java.util.ArrayList;
  4 import java.util.List;
  5 import java.util.Scanner;
  6 
  7 /**
  8  * @Author LZP
  9  * @Date 2021/3/19 11:59
 10  * @Version 1.0
 11  * 题目描述
 12  *
 13  * w 星球的一个种植园,被分成 m×n 个小格子(东西方向 m 行,南北方向 n 列)。每个格子里种了一株合根植物。
 14  *
 15  * 这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。
 16  *
 17  * 如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?
 18  * 输入描述
 19  *
 20  * 第一行,两个整数 m,n,用空格分开,表示格子的行数、列数(1≤m,n≤10000)。
 21  *
 22  * 接下来一行,一个整数 k (0≤k≤10^5 ),表示下面还有 k 行数据。
 23  *
 24  * 接下来 k 行,第行两个整数 a,b,表示编号为 a 的小格子和编号为 b 的小格子合根了。
 25  *
 26  * 格子的编号一行一行,从上到下,从左到右编号。
 27  *
 28  * 比如:5×4 的小格子,编号:
 29  *
 30  * 1 2 3 4
 31  * 5 6 7 8
 32  * 9 10 11 12
 33  * 13 14 15 16
 34  * 17 18 19 20
 35  * 输出描述
 36  *
 37  * 输出植物数量。
 38  * 输入输出样例
 39  * 示例
 40  *
 41  * 输入
 42  * 5 4
 43  * 16
 44  * 2 3
 45  * 1 5
 46  * 5 9
 47  * 4 8
 48  * 7 8
 49  * 9 10
 50  * 10 11
 51  * 11 12
 52  * 10 14
 53  * 12 16
 54  * 14 18
 55  * 17 18
 56  * 15 19
 57  * 19 20
 58  * 9 13
 59  * 13 17
 60  *
 61  * 输出
 62  * 5
 63  *
 64  * 样例说明
 65  *
 66  * 其合根情况参考下图:
 67  *
 68  * 运行限制
 69  *
 70  *     最大运行时间:2s
 71  *     最大运行内存: 256M
 72  *
 73  *  该题使用并查集解,每当输入一对可以合根的植物,就将这两个植物所在的集合合并,直到
 74  *  所有合根的情况都已经输入完成,就确定了这片田地中最终剩下多少根植物,也就是求最
 75  *  终剩下的有几个集合,这里的集合我们用树来表示
 76  */
 77 public class MergePlant {
 78     /**
 79      * dp[i] = val 表示数i此时处在索引为val的位置
 80      */
 81     private static int[] dp = new int[1000000 + 10];
 82     private static Node[] arr = new Node[1000000 + 10];
 83     public static void main(String[] args) {
 84         Scanner input = new Scanner(System.in);
 85         int m = input.nextInt();
 86         int n = input.nextInt();
 87         for (int i = 1; i <= m * n; i++) {
 88             // 一开始,将每个植物都作为一个集合,把自己装入
 89             Node root = new Node(i, null, null);
 90             arr[i] = root;
 91             dp[i] = i;
 92         }
 93 
 94         int k = input.nextInt();
 95         // 接收所有合根的情况
 96         for (int i = 0; i < k; i++) {
 97             int a = input.nextInt();
 98             int b = input.nextInt();
 99             // 将b中的所有数用List集合存储起来,后面会用的到
100             List<Integer> list = new ArrayList<>();
101             if (!find(a, b)) {
102 
103                 // 判断两者能否合根,因为题目说了:这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。
104                 // 言外之意就是,斜的对角线是不可以的
105 
106                 // a、b不在同一棵树中
107                 Node rootA = arr[dp[a]];
108                 Node rootB = arr[dp[b]];
109                 if (rootA != null && rootB != null) {
110                     union(rootA, rootB, list);
111                     // 修改dp[b]中的所有值为dp[a]
112                     for (int j = 0; j < list.size(); j++) {
113                         dp[list.get(j)] = dp[a];
114                         // 置为null
115                         arr[list.get(j)] = null;
116                     }
117                 }
118             }
119         }
120 
121         int count = 0;
122         for (int i = 1; i <= m * n; i++) {
123             // 如果不为arr[i] != null ,则表示i这棵植物还在,没有被其他植物合根,
124             if (arr[i] != null) {
125                 count++;
126             }
127         }
128         System.out.println(count);
129     }
130 
131     /**
132      * 查询两个数是否在同一棵树中
133      * @param a
134      * @param b
135      * @return
136      */
137     public static boolean find(int a, int b) {
138         return dp[a] == dp[b];
139     }
140 
141     /**
142      * 将b树中的所有元素都添加到a树中
143      * @param a
144      * @param b
145      * @return
146      */
147     public static Node union(Node a, Node b, List<Integer> list) {
148         BinarySortTreeUtil.getValues(b, list);
149         for (int i = 0; i < list.size(); i++) {
150             BinarySortTreeUtil.add(a, list.get(i));
151         }
152         return a;
153     }
154 }
155 
156 
157 /**
158  * 二叉排序树
159  */
160 class BinarySortTreeUtil {
161 
162     public static void add(Node root, int data) {
163         if (!find(root, data)) {
164             addNode(root, data);
165         }
166     }
167 
168     /**
169      * 中序遍历
170      * @param root
171      * @param list
172      */
173     public static void getValues(Node root, List<Integer> list) {
174         if (root == null) {
175             return;
176         }
177         if (root.left != null) {
178             getValues(root.left, list);
179         }
180         list.add(root.data);
181         if (root.right != null) {
182             getValues(root.right, list);
183         }
184     }
185 
186     /**
187      * 添加元素
188      * 如果要添加的元素跟二叉树中的某一个元素相等则不添加
189      * @param root
190      * @param data
191      */
192     private static void addNode(Node root, int data) {
193         if (data < root.data) {
194             if (root.left == null) {
195                 // 如果左孩子为空,则直接赋值给左孩子
196                 root.left = new Node(data, null, null);
197             } else {
198                 // 左孩子
199                 addNode(root.left, data);
200             }
201         } else if (data > root.data){
202             if (root.right == null) {
203                 // 如果右孩子为空,则直接赋值给右孩子
204                 root.right = new Node(data, null, null);
205             } else {
206                 // 右孩子
207                 addNode(root.right, data);
208             }
209         }
210     }
211 
212     /**
213      * 二分查找
214      * @param data
215      * @return
216      */
217     public static boolean find(Node root, int data) {
218         if (root == null) {
219             return false;
220         }
221         if (data == root.data) {
222             return true;
223         } else if (data < root.data) {
224             return find(root.left, data);
225         } else {
226             return find(root.right, data);
227         }
228     }
229 }
230 
231 
232 /**
233  * 二叉树节点
234  */
235 class Node {
236     public int data;
237     public Node left;
238     public Node right;
239 
240     public Node(int data, Node left, Node right) {
241         this.data = data;
242         this.left = left;
243         this.right = right;
244     }
245 }

推荐阅读