AdventOfCode 2024 Day 20

Part One

仍然是走迷宫,从起点到终点有唯一的通道。假如可以穿墙的话,那么可以节省多少步?

仍然是走迷宫,类似于Day 16,不过这次有些特殊,仔细观察可以看到,从起点到终点只有唯一的一条路,并没有别的分支可以走。

因此在计算迷宫路径的时候,无需考虑最短路径。

with recursive rows as (
    SELECT (row_number() over ()) :: INTEGER as _row, line
    FROM lance_input
), matrix AS (
    SELECT _row :: INTEGER, x.idx :: INTEGER as _col, x.pos
    FROM rows, regexp_split_to_table(line, '') with ordinality as x(pos, idx)
), cols AS (
    SELECT _col, string_agg(pos, '' order by _row) as line
    FROM matrix
    GROUP BY _col
), rows_range AS (
    SELECT _row, start_pos, end_pos
    FROM rows, find_dot_ranges(line) t
    WHERE t.start_pos < t.end_pos
), cols_range AS (
    SELECT _col, start_pos, end_pos
    FROM cols, find_dot_ranges(line) t
    WHERE t.start_pos < t.end_pos
), rows_new_range AS (
    SELECT t._row, s.start_pos as _col, s.end_pos as next_col
    FROM (
        SELECT a._row, a.start_pos, a.end_pos, b.pos_array
        FROM rows_range a
        LEFT JOIN (
            SELECT a._row, a.start_pos, a.end_pos, array_agg(b._col order by b._col) as pos_array
            FROM rows_range a
            JOIN cols_range b
            ON a._row between b.start_pos and b.end_pos
            AND b._col between a.start_pos and a.end_pos
            GROUP BY a._row, a.start_pos, a.end_pos
        ) b
        ON a._row = b._row
        AND a.start_pos = b.start_pos
        AND a.end_pos = b.end_pos
    ) t, split_range(start_pos, end_pos, pos_array) s
), cols_new_range AS (
    SELECT t._col, s.start_pos as _row, s.end_pos as next_row
    FROM (
        SELECT a._col, a.start_pos, a.end_pos, b.pos_array
        FROM cols_range a
        LEFT JOIN (
            SELECT b._col, b.start_pos, b.end_pos, array_agg(a._row order by a._row) as pos_array
            FROM rows_range a
            JOIN cols_range b
            ON a._row between b.start_pos and b.end_pos
            AND b._col between a.start_pos and a.end_pos
            GROUP BY b._col, b.start_pos, b.end_pos
        ) b
        ON a._col = b._col
        AND a.start_pos = b.start_pos
        AND a.end_pos = b.end_pos
    ) t, split_range(start_pos, end_pos, pos_array) s
), all_range AS (
    SELECT _row as start_row, _col as start_col, _row as end_row, next_col as end_col, 'E' as direction
    FROM rows_new_range

    UNION ALL

    SELECT _row as start_row, next_col as start_col, _row as end_row, _col as end_col, 'W' as direction
    FROM rows_new_range

    UNION ALL

    SELECT _row as start_row, _col as start_col, next_row as end_row, _col as end_col, 'S' as direction
    FROM cols_new_range

    UNION ALL

    SELECT next_row as start_row, _col as start_col, _row as end_row, _col as end_col, 'N' as direction
    FROM cols_new_range
), walkthrough AS (
    SELECT _row, _col, 'E' as direction, 0 as steps, array[_row * 10000 + _col] as path
    FROM matrix
    WHERE pos = 'S'

    UNION ALL

    SELECT *
    FROM (
        WITH walkthrough_inner as (select * From walkthrough)
        SELECT b.end_row as _row, 
               b.end_col as _col, 
               b.direction, 
               a.steps + abs(b.end_row - b.start_row) + abs(b.end_col - b.start_col) as steps,
               path || (b.end_row * 10000 + b.end_col) as path
        FROM walkthrough_inner a
        JOIN all_range b
        ON a._row = b.start_row
        AND a._col = b.start_col
        AND NOT (b.end_row * 10000 + b.end_col) = ANY(a.path)
    ) t
)

之前为了节省计算步骤,walkthrough中的每一行记录,其实是完整路径上的每一个转向节点,比如:

 _row | _col | direction | steps |                                  path                                   
------+------+-----------+-------+-------------------------------------------------------------------------
   48 |   26 | E         |     0 | {480026}
   54 |   26 | S         |     6 | {480026,540026}
   54 |   28 | E         |     8 | {480026,540026,540028}
   50 |   28 | N         |    12 | {480026,540026,540028,500028}
   50 |   30 | E         |    14 | {480026,540026,540028,500028,500030}
   56 |   30 | S         |    20 | {480026,540026,540028,500028,500030,560030}
   56 |   32 | E         |    22 | {480026,540026,540028,500028,500030,560030,560032}
   50 |   32 | N         |    28 | {480026,540026,540028,500028,500030,560030,560032,500032}
   50 |   40 | E         |    36 | {480026,540026,540028,500028,500030,560030,560032,500032,500040}

为了计算“穿墙”之后节省的步数,需要拆分到这条路径经过的每一个节点,并标明达到此节点的步数。

all_steps AS (
    SELECT CASE WHEN last_row is null THEN current_row
                WHEN last_row < current_row THEN last_row + seq
                WHEN last_row > current_row THEN last_row - seq
                ELSE current_row
            END as _row,
           CASE WHEN last_col is null THEN current_col
                WHEN last_col < current_col THEN last_col + seq
                WHEN last_col > current_col THEN last_col - seq
                ELSE current_col
            END as _col,
           row_number() over (order by rn, seq) as rn
    FROM (
        SELECT row_number() over () as rn,
               lag(_row) over() as last_row, 
               lag(_col) over() as last_col,
               _row as current_row,
               _col as current_col
        FROM walkthrough
    ) s, generate_series(1, 15) as seq
    WHERE seq <= abs(last_row - current_row)
    OR seq <= abs(last_col - current_col)
    OR (last_row is null AND seq = 1)
)

经过转换后,完整路径走了多少步,就会有多少条记录,而且步数呈递增。

 _row | _col | rn 
------+------+----
   48 |   26 |  1
   49 |   26 |  2
   50 |   26 |  3
   51 |   26 |  4
   52 |   26 |  5
   53 |   26 |  6
   54 |   26 |  7
   54 |   27 |  8
   54 |   28 |  9
   53 |   28 | 10
(10 rows)

“穿墙“的定义,就是起点和终点,在同一行或者同一列,且两者相差2步。那么节省的步数,就是原始步数差减2即可得出。

SELECT save_steps, count(*) 
FROM (
    SELECT s._row as start_row, s._col as start_col,
           t._row as end_row, t._col as end_col,
           t.rn - s.rn - 2 as save_steps 
    FROM all_steps s
    JOIN all_steps t
    ON s.rn < t.rn
    AND ((abs(s._row - t._row) = 2 AND s._col = t._col)
    OR (abs(s._col - t._col) = 2 AND s._row = t._row)) 
) t 
GROUP BY save_steps;

Part Two

如果“无敌穿墙”的步数不限制在两步,而是20步,那么节省的步数分布是什么样的?

从2变成20,简单转换限制条件即可

SELECT save_steps, count(*)  as cnt
FROM (
    SELECT s._row as start_row, s._col as start_col,
           t._row as end_row, t._col as end_col,
           t.rn - s.rn - (abs(s._row - t._row) + abs(s._col - t._col)) as save_steps 
    FROM all_steps s
    JOIN all_steps t
    ON s.rn < t.rn
    AND abs(s._row - t._row) + abs(s._col - t._col) <= 20 
) t 
GROUP BY save_steps

发表评论