Задача 9 дня Advent of Code — Disk Fragmenter.
😊 Часть I. Описание задачи
На диске из N
секторов хранятся файлы, каждый занимая непрерывный блок секторов. Между блоками могут быть «пробелы» (свободные сектора).
- Задача: последовательно перемещать по одному сектору каждого файла (начиная с конца списка) в самый левый доступный свободный сектор, пока не исчезнут все разрывы.
- Пример:
В начале:
00...111...2...333.44.5555.6666.777.888899
После:
0099811188827773336446555566..............
😊 Часть II. Описание задачи
Теперь вместо по-секторного перемещения перемещаем файлы целиком, один раз для каждого файла, в порядке убывания file_id
. Для каждого файла (перебираем справа-налево) ищем самый левый свободный диапазон длины ≥ размера файла
;
если он находится левее текущего — перемещаем файл, иначе оставляем на месте.
- Пример:
В начале:
00...111...2...333.44.5555.6666.777.888899
После:
00992111777.44.333....5555.6666.....8888..
💡 Идея решения
Строим дерево отрезков над массивом из N
секторов.
В каждой вершине храним три метрики:
prefix_free
— длина свободной приставки,
suffix_free
— длина свободного суффикса,
max_free
— максимальная длина подряд идущих свободных секторов.
Часть I:
- Инициализируем дерево, пометив занятые блоки.
- Для каждого файла (в обратном порядке) освобождаем его исходный диапазон
- Затем по одному сектору: ищем самый левый свободный блок, занимаем текущим файлом, и так пока не упакуем весь файл.
Часть II:
- Инициализируем аналогично.
- Для каждого файла (по убыванию
file_id
) ищем диапазон подходящей длины
- Eсли он левее исходного, освобождаем старый, помечаем новый и «двигаем» файл целиком.
🔍 Детали подхода
- Дерево отрезков:
- Размер дерева:
2·cap
, где cap = next_power_of_two(N)
.
- Листы (индексы
[cap..cap+N)
) изначально помечаются как свободные.
- Рекурсивная инициализация внутренних узлов.
- Операции за
O(log N)
(рекурсивно обрабатываем только необходимые узлы с метриками):
mark_free(range)
mark_busy(range)
find_free_chain(k)
-
Часть I:
- Занятые блоки помечаются в дереве.
- Файлы обрабатываются в порядке убывания (справа-налево).
- Каждый файл:
- Освобождается полностью.
- Далее в цикле ищется ближайший слева свободный сектор (через
find_free_chain(1)
).
- Найденный блок заполняется данными файла (повторяем пока не запишем весь файл).
-
Часть II:
- Занятые блоки помечаются в дереве.
- Файлы перебираются по убыванию
file_id
.
- Для каждого файла:
- Ищется диапазон длины
≥ leni
, причём только если он находится левее текущей позиции.
- В случае успеха освобождается старый диапазон, и файл переносится целиком в новую позицию.
⏱️ Асимптотика
-
Временная сложность:
- Построение дерева:
O(N)
- Поиск/обновление диапазона:
O(log N)
- Part I:
O(N + U · log N)
, где U = Σ leni
— общий объём занятого пространства на диске.
- Part II:
O(N + M · log N)
, где M
— число файлов.
-
Память:
O(N)
- (хранится
2·cap = O(N)
узлов сегментного дерева).
💻 Исходный код
Решение
use super::TaskData;
use super::segment_tree::{FreeChainTree, Chain};
pub struct Solution;
/// Fields: file_id, block_start, block_len
pub type MemoryMap = Vec<(usize, usize, usize)>;
impl Solution {
/// Part I: Move individual blocks (1 sector at a time) to the leftmost free block,
/// eliminating gaps between file blocks.
pub fn solve_simple(data: &TaskData) -> usize {
let mut mgr = Self::init_chain_tree(data);
// Build a new map by moving blocks one by one, from last file to first
let defrag_map: MemoryMap = data.disk_dump.iter().rev()
.flat_map(|&(file_id, start, len)| {
// Free the original file segments
mgr.mark_free(start .. start + len);
// Move one sector at a time to the next free position
let mut moved = Vec::new();
let mut remaining = len;
while remaining > 0 {
// find a free block of size >= 1
let Chain { start: new_pos, len: free_len } =
mgr.find_free_chain(1)
.expect("No free space during simple defrag");
let block_len = remaining.min(free_len);
// occupy the block
mgr.mark_busy(new_pos .. new_pos + block_len);
moved.push((file_id, new_pos, block_len));
remaining -= block_len;
}
moved
}).collect();
Self::check_sum(&defrag_map)
}
/// Part II: Move whole files once, in order of decreasing file ID,
/// to the leftmost free span large enough for the file (if any).
pub fn solve_hard(data: &TaskData) -> usize {
let mut mgr = Self::init_chain_tree(data);
let defrag_map = data.disk_dump.iter().rev().map(|&(file_id, start, len)|
if let Some(Chain{start:new_pos, .. }) = mgr.find_free_chain(len)
.filter(|chain| chain.start < start)
{
// move whole file
mgr.mark_free(start .. start + len);
mgr.mark_busy(new_pos .. new_pos + len);
(file_id, new_pos, len)
} else {
(file_id, start, len) // no move possible
}).collect();
Self::check_sum(&defrag_map)
}
/// Initialize a FreeChainTree by marking all occupied ranges busy
fn init_chain_tree(data: &TaskData) -> FreeChainTree {
let mut mgr = FreeChainTree::new(data.disk_size);
// Mark all occupied sectors busy
for &(_, start, len) in &data.disk_dump {
mgr.mark_busy(start .. start + len);
}
mgr
}
/// Checksum: sum over each segment: file_id * sum_{i=0 to len-1}(offset + i)
fn check_sum(map: &MemoryMap) -> usize {
map.iter()
.map(|&(file_id, offset, len)| {
// sum of arithmetic sequence:
let seq_sum = offset * len + len * (len - 1) / 2;
seq_sum * file_id
}).sum()
}
}
Дерево Отрезков
use std::ops::Range;
/// A contiguous chain of free sectors: starting index and length.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Chain {
pub start: usize,
pub len: usize,
}
/// Node storing prefix, suffix, and max free lengths for a segment.
#[derive(Clone, Debug, Default)]
struct MetricsNode {
prefix_free: usize,
suffix_free: usize,
max_free: usize,
}
/// Segment tree managing free-sector chains over `n` sectors.
pub struct FreeChainTree {
n: usize, // original disk size
cap: usize, // rounded-up power-of-two capacity
tree: Vec<MetricsNode>,
}
impl FreeChainTree {
/// Build a new tree for `n` sectors (all free initially).
pub fn new(n: usize) -> Self {
let cap = n.next_power_of_two();
let mut tree = vec![MetricsNode::default(); 2 * cap];
// Initialize leaves corresponding to real sectors
tree[cap..cap+n].fill(MetricsNode::new(1));
// Build internal nodes recursively
Self::build_internal_nodes(&mut tree, 1, 0, cap);
FreeChainTree { n, cap, tree }
}
/// Mark all sectors in `range` as free.
pub fn mark_free(&mut self, range: Range<usize>) {
self.update_range(1, 0..self.cap, range, true);
}
/// Mark all sectors in `range` as busy.
pub fn mark_busy(&mut self, range: Range<usize>) {
self.update_range(1, 0..self.cap, range, false);
}
/// Find the leftmost free chain of length ≥ `k`.
pub fn find_free_chain(&self, k: usize) -> Option<Chain> {
if self.tree[1].max_free < k {
return None;
}
self.find_rec(1, 0..self.cap, k)
}
/// Recursively build internal nodes from leaves.
fn build_internal_nodes(tree: &mut [MetricsNode], idx: usize, l: usize, r: usize) {
if r - l == 1 { return; } // leaf
let mid = Self::mid(l, r);
let (left, right) = Self::child_indices(idx);
Self::build_internal_nodes(tree, left, l, mid);
Self::build_internal_nodes(tree, right, mid, r);
let child_len = mid - l;
tree[idx] = MetricsNode::merge(&tree[left], &tree[right], child_len);
}
/// Update a segment `seg` with `range`, setting free or busy.
fn update_range(&mut self, idx: usize, seg: Range<usize>, range: Range<usize>, free: bool) {
if range.end <= seg.start || range.start >= seg.end {
return;
}
if seg.end == seg.start + 1 {
let len = if free { 1 } else { 0 };
self.tree[idx] = MetricsNode::new(len);
return;
}
let mid = Self::mid(seg.start, seg.end);
let (left, right) = Self::child_indices(idx);
self.update_range(left, seg.start..mid, range.clone(), free);
self.update_range(right, mid..seg.end, range, free);
let child_len = mid - seg.start;
self.tree[idx] = MetricsNode::merge(&self.tree[left], &self.tree[right], child_len);
}
/// Internal recursive search for a free chain of size `≥k`.
fn find_rec(&self, idx: usize, seg: Range<usize>, k: usize) -> Option<Chain> {
let node = &self.tree[idx];
if node.max_free < k {
return None;
}
if node.prefix_free >= k {
return Some(Chain { start: seg.start, len: node.prefix_free });
}
let mid = Self::mid(seg.start, seg.end);
let (left, right) = Self::child_indices(idx);
let middle_space = self.tree[left].suffix_free + self.tree[right].prefix_free;
if self.tree[left].max_free >= k {
self.find_rec(left, seg.start..mid, k)
} else if middle_space >= k {
Some(Chain { start: mid - self.tree[left].suffix_free, len: middle_space })
} else {
self.find_rec(right, mid..seg.end, k)
}
}
/// Compute midpoint of [l..r).
#[inline]
fn mid(l: usize, r: usize) -> usize {
(l + r) >> 1
}
/// Compute (left, right) child indices for `idx`.
#[inline]
fn child_indices(idx: usize) -> (usize, usize) {
let left = idx << 1;
(left, left + 1)
}
}
impl MetricsNode {
/// Leaf constructor: all free of length `free_len`.
fn new(free_len: usize) -> Self {
MetricsNode { prefix_free: free_len, suffix_free: free_len, max_free: free_len }
}
/// Merge two children summaries into parent summary.
fn merge(left: &MetricsNode, right: &MetricsNode, child_len: usize) -> MetricsNode {
let prefix_free = left.prefix_free
+ if left.prefix_free == child_len { right.prefix_free } else { 0 };
let suffix_free = right.suffix_free
+ if right.suffix_free == child_len { left.suffix_free } else { 0 };
let middle_space = left.suffix_free + right.prefix_free;
// max of left, right, or crossing span
let max_free = left.max_free.max(right.max_free).max(middle_space);
MetricsNode { prefix_free, suffix_free, max_free }
}
}
Tags: #rust #algorithms #segmenttree