главная новое лучшее написать
2

Задача 8-го дня Advent of Code — Resonant Collinearity.

🧩 Часть I. Условие задачи

В городе расставлены антенны, каждая из которых настроена на определённую частоту, обозначенную символом (az,AZ,09). Если две антенны одной частоты расположены на прямой, и одна из них находится ровно в два раза дальше от точки, чем другая, то в этой точке возникает антинод.

Нужно найти количество уникальных позиций на карте, в которых возникает антинод. Позиции должны лежать в пределах карты. Антенны с разными частотами не создают антиноду.

💡 Идея

Для каждой частоты рассматриваем все пары антенн, стоящих на одной частоте. Каждая пара порождает ровно два симметричных антинода, расположенных по формуле:

(2*r0 - r1, 2*c0 - c1) и (2*r1 - r0, 2*c1 - c0)

Затем добавляем эти позиции в хеш-сет, если они находятся в пределах границ карты.

🧩 Часть II. Условие задачи

В обновлённой модели антиноды возникают не только в особых симметричных точках, но на всех узлах, достижимых по вектору, соединяющему две антенны одинаковой частоты.

Так например, T-частотные антенны на следующем рисунке образуют антиноды, помеченные знаком # (кроме того антиноды также образуются и в локациях с антеннами).

T....#....
...T......
.T....#...
.........#
..#.......
..........
...#......
..........
....#.....
..........

Задача: определить общее количество уникальных антинод, лежащих внутри границ карты.

💡 Идея

Для каждой пары антенн с одинаковой частотой определяем вектор направления (dr, dc) и, начиная от каждой из антенн, распространяемся по этому вектору вперёд и назад, пока остаёмся в пределах карты. Каждую достигнутую точку заносим в хеш-сет.

🛠 Детали подхода

  1. Структура AntiNodes хранит размеры карты и HashSet для уникальных позиций.
  2. checked_add гарантирует, что добавляемая точка не выходит за границы.
  3. Для перебора всех пар антенн одной частоты используем itertools::tuple_combinations()
  4. В solve_hard в цикле перебираем узлы сетки в обе стороны от каждой пары антенн.

📈 Асимптотика

Введём обозначения

Тогда:

🧾 Исходный код

use super::TaskData;
use std::collections::HashSet;
use itertools::Itertools;

pub struct Solution;

struct AntiNodes {
    rows: i32,
    cols: i32,
    positions: HashSet<(i32, i32)>,
}

impl Solution {
    // Part I: antinode occurs only when one antenna is twice as far as the other on a line
    pub fn solve_simple(data: &TaskData) -> i32 {
        let mut antinodes = AntiNodes::new(data.rows, data.cols);

        for (_, positions) in &data.antenna_lists {
            for (&(r0, c0), &(r1, c1)) in positions.iter().tuple_combinations() {
                // Compute symmetric antinode positions
                antinodes.checked_add(2 * r0 - r1, 2 * c0 - c1);
                antinodes.checked_add(2 * r1 - r0, 2 * c1 - c0);
            }
        }

        antinodes.count() as _
    }

    // Part II: antinode occurs along the entire line through any two same-frequency antennas
    pub fn solve_hard(data: &TaskData) -> i32 {
        let mut antinodes = AntiNodes::new(data.rows, data.cols);

        for (_, positions) in &data.antenna_lists {
            for (&(r0, c0), &(r1, c1)) in positions.iter().tuple_combinations() {
                let (dr, dc) = (r1 - r0, c1 - c0);

                // Extend line forward from (r1, c1)
                let (mut r, mut c) = (r1, c1);
                while antinodes.checked_add(r, c) {
                    r += dr;
                    c += dc;
                }

                // Extend line backward from (r0, c0)
                let (mut r, mut c) = (r0, c0);
                while antinodes.checked_add(r, c) {
                    r -= dr;
                    c -= dc;
                }
            }
        }

        antinodes.count() as _
    }
}

impl AntiNodes {
    fn new(rows: i32, cols: i32) -> Self {
        Self {
            rows,
            cols,
            positions: HashSet::new(),
        }
    }

    // Adds position if it's within map bounds
    fn checked_add(&mut self, r: i32, c: i32) -> bool {
        if (0..self.rows).contains(&r) && (0..self.cols).contains(&c) {
            self.positions.insert((r, c));
            true
        } else {
            false
        }
    }

    fn count(&self) -> usize {
        self.positions.len()
    }
}

Tags: #rust #algorithms #adventofcode #hashset