Part One
输入若干个立方体,计算这些立方体的表面积。
2,2,2
1,2,2
3,2,2
比较简单,只需要计算每个方块有多少个相邻即可
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,剩余的均是空气
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,可以判定空气属于内部
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
)
找出与之相邻的面积数即可
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即为内部,这个判断很不准确。
寻找外部空气
那么换种思路,从外部空气着手,与之相邻的空气也是外部,计算出所有的外部,剩余的即为内部。
从最外缘着手,边界的空气肯定属于外部
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
后续的迭代,寻找与之相邻的空气方块,终止条件就是没有发现更新的空气节点了
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为
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)举例来说,其相邻的方块如下
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轴向上穿越一条边,向下穿越了两条边,因此并不算内部。