Part One
输入若干个立方体,计算这些立方体的表面积。
2,2,2
1,2,2
3,2,2
比较简单,只需要计算每个方块有多少个相邻即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | WITH origin AS ( SELECT row_number() over () as rn, split_part(line, ',' , 1) :: INTEGER as x, split_part(line, ',' , 2) :: INTEGER as y, split_part(line, ',' , 3) :: INTEGER as z FROM lance_input ) SELECT ( select COUNT (*) from origin) * 6 - sum (cnt) FROM ( SELECT m.rn, count (*) as cnt FROM origin m, origin n WHERE m.rn != n.rn AND ((m.x = n.x AND m.y = n.y AND abs (m.z - n.z) = 1) OR (m.x = n.x AND m.z = n.z AND abs (m.y - n.y) = 1) OR (m.y = n.y AND m.z = n.z AND abs (m.x - n.x) = 1)) GROUP BY m.rn ) t |
Part Two
方块内部有空气,空气接触的表面积需要剔除掉。
寻找内部空气
首先,输入只给了lava的方块,并没有给出空气的坐标。那么从全部坐标中,去除掉lava,剩余的均是空气
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | air AS ( SELECT row_number() over () as seq, m.x, m.y, m.z FROM ( SELECT x.rn as x, y.rn as y, z.rn as z FROM ( select generate_series(0, ( select max (x) from origin)) as rn) x, ( select generate_series(0, ( select max (y) from origin)) as rn) y, ( select generate_series(0, ( select max (z) from origin)) as rn) z ) m LEFT JOIN origin n ON m.x = n.x AND m.y = n.y AND m.z = n.z WHERE n.rn is null ) |
既然判断一个空气是否在内部,那么上下左右前后6个方向上均有lava,可以判定空气属于内部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | lava_air_mapping AS ( SELECT m.rn as lava_seq, n.x, n.y, n.z, n.seq as air_seq, CASE WHEN m.y = n.y AND m.z = n.z AND m.x < n.x THEN 'L' WHEN m.y = n.y AND m.z = n.z AND m.x > n.x THEN 'R' WHEN m.x = n.x AND m.z = n.z AND m.y < n.y THEN 'D' WHEN m.x = n.x AND m.z = n.z AND m.y > n.y THEN 'U' WHEN m.x = n.x AND m.y = n.y AND m.z < n.z THEN 'F' WHEN m.x = n.x AND m.y = n.y AND m.z > n.z THEN 'B' END as pos FROM origin m, air n ), inner_air AS ( SELECT air_seq, x, y, z FROM lava_air_mapping GROUP BY air_seq, x, y, z HAVING COUNT ( distinct pos) = 6 ) |
找出与之相邻的面积数即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | WITH origin AS ( SELECT row_number() over () as rn, split_part(line, ',' , 1 ) :: INTEGER as x, split_part(line, ',' , 2 ) :: INTEGER as y, split_part(line, ',' , 3 ) :: INTEGER as z FROM lance_input ), air AS ( SELECT row_number() over () as seq, m.x, m.y, m.z FROM ( SELECT x.rn as x, y.rn as y, z.rn as z FROM (select generate_series( 0 , (select max(x) from origin)) as rn) x, (select generate_series( 0 , (select max(y) from origin)) as rn) y, (select generate_series( 0 , (select max(z) from origin)) as rn) z ) m LEFT JOIN origin n ON m.x = n.x AND m.y = n.y AND m.z = n.z WHERE n.rn is null ), lava_air_mapping AS ( SELECT m.rn as lava_seq, n.x, n.y, n.z, n.seq as air_seq, CASE WHEN m.y = n.y AND m.z = n.z AND m.x < n.x THEN 'L' WHEN m.y = n.y AND m.z = n.z AND m.x > n.x THEN 'R' WHEN m.x = n.x AND m.z = n.z AND m.y < n.y THEN 'D' WHEN m.x = n.x AND m.z = n.z AND m.y > n.y THEN 'U' WHEN m.x = n.x AND m.y = n.y AND m.z < n.z THEN 'F' WHEN m.x = n.x AND m.y = n.y AND m.z > n.z THEN 'B' END as pos FROM origin m, air n ), inner_air AS ( SELECT air_seq, x, y, z FROM lava_air_mapping GROUP BY air_seq, x, y, z HAVING COUNT(distinct pos) = 6 ) SELECT COUNT(*) FROM origin m JOIN inner_air n ON ((m.x = n.x AND m.y = n.y AND abs(m.z - n.z) = 1 ) OR (m.x = n.x AND m.z = n.z AND abs(m.y - n.y) = 1 ) OR (m.y = n.y AND m.z = n.z AND abs(m.x - n.x) = 1 )) |
可是,计算出来的接触面积明显偏大,应该是计算内部空气偏多了。很多不是内部的空气并计入了内部,因此之前的判断,6个方向上均有lava即为内部,这个判断很不准确。
寻找外部空气
那么换种思路,从外部空气着手,与之相邻的空气也是外部,计算出所有的外部,剩余的即为内部。
从最外缘着手,边界的空气肯定属于外部
1 2 3 4 5 6 7 8 | SELECT array_agg(seq) as arr, 0 as size FROM ( SELECT seq FROM air WHERE x IN (0, ( select max (x) from origin)) OR y IN (0, ( select max (y) from origin)) OR z IN (0, ( select max (z) from origin)) ) t |
后续的迭代,寻找与之相邻的空气方块,终止条件就是没有发现更新的空气节点了
1 2 3 4 5 6 7 8 9 10 11 12 13 | WITH new_outer_seq AS ( SELECT z.seq FROM outer_seq x JOIN air y ON x.seq = y.seq JOIN air z ON ((y.x = z.x AND y.y = z.y AND abs (y.z - z.z) = 1) OR (y.x = z.x AND y.z = z.z AND abs (y.y - z.y) = 1) OR (y.y = z.y AND y.z = z.z AND abs (y.x - z.x) = 1)) LEFT JOIN outer_seq o ON z.seq = o.seq WHERE o.seq IS NULL ) |
完整SQL为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | WITH RECURSIVE origin AS ( SELECT row_number() over () as rn, split_part(line, ',' , 1) :: INTEGER as x, split_part(line, ',' , 2) :: INTEGER as y, split_part(line, ',' , 3) :: INTEGER as z FROM lance_input ), air AS ( SELECT row_number() over () as seq, m.x, m.y, m.z FROM ( SELECT x.rn as x, y.rn as y, z.rn as z FROM ( select generate_series(0, ( select max (x) from origin)) as rn) x, ( select generate_series(0, ( select max (y) from origin)) as rn) y, ( select generate_series(0, ( select max (z) from origin)) as rn) z ) m LEFT JOIN origin n ON m.x = n.x AND m.y = n.y AND m.z = n.z WHERE n.rn is null ), outer_air AS ( SELECT array_agg(seq) as arr, 0 as size FROM ( SELECT seq FROM air WHERE x IN (0, ( select max (x) from origin)) OR y IN (0, ( select max (y) from origin)) OR z IN (0, ( select max (z) from origin)) ) t UNION ALL SELECT arr, #arr as size FROM ( WITH outer_seq AS ( SELECT unnest(arr) as seq FROM outer_air ), new_outer_seq AS ( SELECT z.seq FROM outer_seq x JOIN air y ON x.seq = y.seq JOIN air z ON ((y.x = z.x AND y.y = z.y AND abs (y.z - z.z) = 1) OR (y.x = z.x AND y.z = z.z AND abs (y.y - z.y) = 1) OR (y.y = z.y AND y.z = z.z AND abs (y.x - z.x) = 1)) LEFT JOIN outer_seq o ON z.seq = o.seq WHERE o.seq IS NULL ) SELECT array_agg(seq) :: INTEGER [] as arr FROM ( SELECT seq FROM outer_seq UNION SELECT seq FROM new_outer_seq ) t WHERE exists ( select * from new_outer_seq) ) t WHERE #arr is not null ) SELECT COUNT (*) FROM origin m JOIN ( SELECT m.seq, x, y, z FROM air m LEFT JOIN ( SELECT unnest(arr) as seq from ( select arr from outer_air order by size desc limit 1) n) n ON m.seq = n.seq WHERE n.seq IS NULL ) n ON ((m.x = n.x AND m.y = n.y AND abs (m.z - n.z) = 1) OR (m.x = n.x AND m.z = n.z AND abs (m.y - n.y) = 1) OR (m.y = n.y AND m.z = n.z AND abs (m.x - n.x) = 1)) |
内部计算错误的原因
根据两种办法,发现有6个方块被错误地计算为内部。以(9, 20, 11)举例来说,其相邻的方块如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | x轴的lava rn | x | y | z ------+----+----+---- 478 | 7 | 20 | 11 1381 | 10 | 20 | 11 985 | 11 | 20 | 11 1070 | 12 | 20 | 11 1728 | 13 | 20 | 11 y轴的lava rn | x | y | z ------+---+----+---- 1074 | 9 | 1 | 11 640 | 9 | 2 | 11 825 | 9 | 17 | 11 691 | 9 | 18 | 11 323 | 9 | 19 | 11 837 | 9 | 21 | 11 z轴的lava rn | x | y | z ------+---+----+---- 744 | 9 | 20 | 10 1005 | 9 | 20 | 12 |
问题就出在y轴,按照point in polygon的算法,y轴向上穿越一条边,向下穿越了两条边,因此并不算内部。