|
@ -2,6 +2,7 @@ use rand::prelude::{StdRng, SliceRandom}; |
|
|
use rand::{SeedableRng, Rng, thread_rng}; |
|
|
use rand::{SeedableRng, Rng, thread_rng}; |
|
|
use std::fmt; |
|
|
use std::fmt; |
|
|
use std::fmt::Formatter; |
|
|
use std::fmt::Formatter; |
|
|
|
|
|
use std::collections::{HashMap, HashSet}; |
|
|
|
|
|
|
|
|
pub const SIZE: usize = 16384; |
|
|
pub const SIZE: usize = 16384; |
|
|
pub const HOUSE_RANGE: usize = 500; |
|
|
pub const HOUSE_RANGE: usize = 500; |
|
@ -95,6 +96,20 @@ impl fmt::Display for Rectangle { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl Rectangle { |
|
|
|
|
|
pub fn is_inside(&self, x: usize, y: usize) -> bool { |
|
|
|
|
|
self.left <= x && x <= self.right && self.top <= y && y <= self.bottom |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn width(&self) -> usize { |
|
|
|
|
|
self.right - self.left |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn height(&self) -> usize { |
|
|
|
|
|
self.bottom - self.top |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
pub struct HouseLayout<'a> { |
|
|
pub struct HouseLayout<'a> { |
|
|
city: &'a City, |
|
|
city: &'a City, |
|
|
reachable: Vec<u16>, |
|
|
reachable: Vec<u16>, |
|
@ -166,108 +181,292 @@ impl<'a> HouseLayout<'a> { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn dump_layout(layout: &HouseLayout, best_price: &mut Option<u32>, seed: u64) { |
|
|
|
|
|
let price = layout.price(); |
|
|
|
|
|
if best_price.is_none() || price < best_price.unwrap() { |
|
|
|
|
|
*best_price = Some(price); |
|
|
|
|
|
eprintln!("Printing {} - new best", price); |
|
|
|
|
|
println!("New best!"); |
|
|
|
|
|
println!("Price {}, seed {}", price, seed); |
|
|
|
|
|
print_houses(&layout.houses()); |
|
|
|
|
|
println!(); |
|
|
|
|
|
} else { |
|
|
|
|
|
eprintln!("Printing {}", price); |
|
|
|
|
|
println!("Price {}, seed {}", price, seed); |
|
|
|
|
|
print_houses(&layout.houses()); |
|
|
|
|
|
println!(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn main() { |
|
|
fn main() { |
|
|
let city = City::read_from_file("01.in"); |
|
|
let city = City::read_from_file("01.in"); |
|
|
|
|
|
|
|
|
let mut best_price: Option<u32> = None; |
|
|
let mut best_price: Option<u32> = None; |
|
|
|
|
|
|
|
|
loop { |
|
|
loop { |
|
|
let seed: u64 = thread_rng().gen(); |
|
|
let seed: u64 = thread_rng().gen(); |
|
|
eprintln!("Starting seed {}", seed); |
|
|
eprintln!("Starting seed {}", seed); |
|
|
|
|
|
|
|
|
let mut rng = StdRng::seed_from_u64(seed); |
|
|
let mut rng = StdRng::seed_from_u64(seed); |
|
|
let mut layout = HouseLayout::new(&city); |
|
|
let mut layout = HouseLayout::new(&city); |
|
|
|
|
|
eprintln!("Starting random population..."); |
|
|
|
|
|
populate_random(&mut layout, &mut rng); |
|
|
|
|
|
eprintln!("Finished random init, price: {}", layout.price()); |
|
|
loop { |
|
|
loop { |
|
|
loop { |
|
|
let mut improved = false; |
|
|
let x = rng.gen_range(0..SIZE); |
|
|
eprintln!("Starting moving individual houses..."); |
|
|
let y = rng.gen_range(0..SIZE); |
|
|
if improve_move_individual_houses(&mut layout, &mut rng) { |
|
|
let house = House::new(x, y); |
|
|
dump_layout(&layout, &mut best_price, seed); |
|
|
if city.is_house_xy(x, y) && !layout.is_covered(house) { |
|
|
improved = true; |
|
|
layout.add_house(house); |
|
|
} |
|
|
break; |
|
|
eprintln!("Finished moving individual houses..."); |
|
|
} |
|
|
eprintln!("Starting pairwise house merge..."); |
|
|
|
|
|
if improve_merge_pairwise(&mut layout) { |
|
|
|
|
|
dump_layout(&layout, &mut best_price, seed); |
|
|
|
|
|
improved = true; |
|
|
} |
|
|
} |
|
|
|
|
|
eprintln!("Finished pairwise house merge"); |
|
|
|
|
|
if !improved { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if layout.is_valid() { |
|
|
fn populate_random(layout: &mut HouseLayout, rng: &mut StdRng) { |
|
|
|
|
|
loop { |
|
|
|
|
|
loop { |
|
|
|
|
|
let x = rng.gen_range(0..SIZE); |
|
|
|
|
|
let y = rng.gen_range(0..SIZE); |
|
|
|
|
|
let house = House::new(x, y); |
|
|
|
|
|
if layout.city.is_house_xy(x, y) && !layout.is_covered(house) { |
|
|
|
|
|
layout.add_house(house); |
|
|
break; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
eprintln!("Finished random init, price: {}", layout.price()); |
|
|
if layout.is_valid() { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
let mut untried_houses = layout.houses().clone(); |
|
|
fn improve_move_individual_houses(layout: &mut HouseLayout, mut rng: &mut StdRng) -> bool { |
|
|
untried_houses.shuffle(&mut rng); |
|
|
let mut improved = false; |
|
|
|
|
|
let mut untried_houses = layout.houses().clone(); |
|
|
while untried_houses.len() > 0 { |
|
|
untried_houses.shuffle(&mut rng); |
|
|
let house = untried_houses.pop().unwrap(); |
|
|
|
|
|
let mut house_index = layout.houses().iter().position(|x| *x == house).unwrap(); |
|
|
while untried_houses.len() > 0 { |
|
|
|
|
|
let house = untried_houses.pop().unwrap(); |
|
|
let move_rectangle = match get_valid_move_rectangle(&layout, house) { |
|
|
let house_index = layout.houses().iter().position(|x| *x == house).unwrap(); |
|
|
Ok(move_rectangle) => move_rectangle, |
|
|
|
|
|
Err(RectangleSearchError::UselessHouse) => { |
|
|
let move_rectangle = match get_valid_move_rectangle(&layout, house) { |
|
|
let old_price = layout.price(); |
|
|
Ok(move_rectangle) => move_rectangle, |
|
|
layout.remove_house(house_index); |
|
|
Err(RectangleSearchError::Useless) => { |
|
|
let new_price = layout.price(); |
|
|
//let old_price = layout.price();
|
|
|
let price_diff = new_price as i64 - old_price as i64; |
|
|
layout.remove_house(house_index); |
|
|
//eprintln!(" candidate is valid, price diff: {}.", price_diff);
|
|
|
//let new_price = layout.price();
|
|
|
//eprintln!("Removed a house (useless), diff {}", price_diff);
|
|
|
//let price_diff = new_price as i64 - old_price as i64;
|
|
|
//eprintln!("Improved price: {}", new_price);
|
|
|
//eprintln!(" candidate is valid, price diff: {}.", price_diff);
|
|
|
untried_houses = layout.houses().clone(); |
|
|
//eprintln!("Removed a house (useless), diff {}", price_diff);
|
|
|
untried_houses.shuffle(&mut rng); |
|
|
//eprintln!("Improved price: {}", new_price);
|
|
|
continue; |
|
|
improved = true; |
|
|
|
|
|
untried_houses = layout.houses().clone(); |
|
|
|
|
|
untried_houses.shuffle(&mut rng); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
_ => unreachable!() |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// TODO: Not needed, can just store best
|
|
|
|
|
|
let mut new_candidates = Vec::new(); |
|
|
|
|
|
for new_y in move_rectangle.top..=move_rectangle.bottom { |
|
|
|
|
|
for new_x in move_rectangle.left..=move_rectangle.right { |
|
|
|
|
|
if layout.city.is_house_xy(new_x, new_y) && layout.city.get_price_xy(new_x, new_y) < layout.city.get_price(&house) { |
|
|
|
|
|
new_candidates.push(House::new(new_x, new_y)); |
|
|
} |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
new_candidates.sort_by(|a, b| layout.city.get_price(&a).cmp(&layout.city.get_price(&b))); |
|
|
|
|
|
if new_candidates.len() == 0 { |
|
|
|
|
|
//eprintln!("Did not find candidate");
|
|
|
|
|
|
} else { |
|
|
|
|
|
for (i, &candidate) in new_candidates.iter().enumerate() { |
|
|
|
|
|
//eprint!("Found candidate {}...", i);
|
|
|
|
|
|
|
|
|
|
|
|
//let old_price = layout.price();
|
|
|
|
|
|
layout.remove_house(house_index); |
|
|
|
|
|
layout.add_house(candidate); |
|
|
|
|
|
|
|
|
|
|
|
assert!(layout.is_valid()); |
|
|
|
|
|
//let new_price = layout.price();
|
|
|
|
|
|
//let price_diff = new_price as i64 - old_price as i64;
|
|
|
|
|
|
//eprintln!(" candidate is valid, price diff: {}.", price_diff);
|
|
|
|
|
|
//eprintln!("Improved price: {}", new_price);
|
|
|
|
|
|
improved = true; |
|
|
|
|
|
untried_houses = layout.houses().clone(); |
|
|
|
|
|
untried_houses.shuffle(&mut rng); |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
improved |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut new_candidates = Vec::new(); |
|
|
pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool { |
|
|
for new_y in move_rectangle.top..=move_rectangle.bottom { |
|
|
let mut improved = false; |
|
|
for new_x in move_rectangle.left..=move_rectangle.right { |
|
|
|
|
|
if city.is_house_xy(new_x, new_y) && city.get_price_xy(new_x, new_y) < city.get_price(&house) { |
|
|
loop { |
|
|
new_candidates.push(House::new(new_x, new_y)); |
|
|
// This here is a hack for being unable to modify the houses while looping through them.
|
|
|
|
|
|
// We instead go through the houses repeatedly and remember which pairs we have already
|
|
|
|
|
|
// tried by hashing their values because they can and do move throughout the layout Vec
|
|
|
|
|
|
// as it's being modified.
|
|
|
|
|
|
let mut checked = HashSet::new(); |
|
|
|
|
|
|
|
|
|
|
|
let mut loop_improved = false; |
|
|
|
|
|
loop { |
|
|
|
|
|
let mut merge = None; |
|
|
|
|
|
|
|
|
|
|
|
'outer_houses: for i in 0..layout.houses().len() { |
|
|
|
|
|
for j in i + 1..layout.houses().len() { |
|
|
|
|
|
let house1 = layout.houses()[i]; |
|
|
|
|
|
let house2 = layout.houses()[j]; |
|
|
|
|
|
|
|
|
|
|
|
let x_dist = (house1.x as i32 - house2.x as i32).abs() as usize; |
|
|
|
|
|
let y_dist = (house1.y as i32 - house2.y as i32).abs() as usize; |
|
|
|
|
|
if x_dist > 4 * HOUSE_RANGE || y_dist > 4 * HOUSE_RANGE { |
|
|
|
|
|
// Never close enough to merge
|
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if checked.contains(&(house1, house2)) || checked.contains(&(house2, house1)) { |
|
|
|
|
|
continue; |
|
|
|
|
|
} else { |
|
|
|
|
|
checked.insert((house1, house2)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
match get_valid_move_rectangle_multiple(&layout, &vec! {house1, house2}) { |
|
|
|
|
|
Ok(rect) => { |
|
|
|
|
|
let mut cheapest = None; |
|
|
|
|
|
for y in rect.top..=rect.bottom { |
|
|
|
|
|
for x in rect.left..=rect.right { |
|
|
|
|
|
if !layout.city.is_house_xy(x, y) { continue; } |
|
|
|
|
|
let price = layout.city.get_price_xy(x, y); |
|
|
|
|
|
match cheapest { |
|
|
|
|
|
None => cheapest = Some((x, y, price)), |
|
|
|
|
|
Some((_, _, cheapest_price)) if price < cheapest_price => cheapest = Some((x, y, price)), |
|
|
|
|
|
_ => {} |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if let Some((x, y, price)) = cheapest { |
|
|
|
|
|
if price >= layout.city.get_price(&house1) + layout.city.get_price(&house2) { |
|
|
|
|
|
// Merging not worth
|
|
|
|
|
|
//eprintln!("Merging not worth!");
|
|
|
|
|
|
} else { |
|
|
|
|
|
merge = Some((i, j, House::new(x, y))); |
|
|
|
|
|
break 'outer_houses; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
Err(RectangleSearchError::Useless) => eprintln!("Found useless pair of houses, not solving!"), |
|
|
|
|
|
Err(RectangleSearchError::Unsatisfiable) => {} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
new_candidates.sort_by(|a, b| city.get_price(&a).cmp(&city.get_price(&b))); |
|
|
if let Some((i, j, house)) = merge { |
|
|
if new_candidates.len() == 0 { |
|
|
let old_price = layout.price(); |
|
|
//eprintln!("Did not find candidate");
|
|
|
assert!(i < j); |
|
|
|
|
|
layout.remove_house(j); |
|
|
|
|
|
layout.remove_house(i); |
|
|
|
|
|
layout.add_house(house); |
|
|
|
|
|
|
|
|
|
|
|
assert!(layout.is_valid()); |
|
|
|
|
|
let new_price = layout.price(); |
|
|
|
|
|
let price_diff = new_price as i32 - old_price as i32; |
|
|
|
|
|
eprintln!("Merged two houses, new price {}, diff {}", new_price, price_diff); |
|
|
|
|
|
improved = true; |
|
|
|
|
|
loop_improved = true; |
|
|
} else { |
|
|
} else { |
|
|
for (i, &candidate) in new_candidates.iter().enumerate() { |
|
|
break; |
|
|
//eprint!("Found candidate {}...", i);
|
|
|
|
|
|
|
|
|
|
|
|
let old_price = layout.price(); |
|
|
|
|
|
layout.remove_house(house_index); |
|
|
|
|
|
layout.add_house(candidate); |
|
|
|
|
|
|
|
|
|
|
|
assert!(layout.is_valid()); |
|
|
|
|
|
let new_price = layout.price(); |
|
|
|
|
|
let price_diff = new_price as i64 - old_price as i64; |
|
|
|
|
|
//eprintln!(" candidate is valid, price diff: {}.", price_diff);
|
|
|
|
|
|
//eprintln!("Improved price: {}", new_price);
|
|
|
|
|
|
untried_houses = layout.houses().clone(); |
|
|
|
|
|
untried_houses.shuffle(&mut rng); |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let price = layout.price(); |
|
|
if !loop_improved { |
|
|
if best_price.is_none() || price < best_price.unwrap() { |
|
|
break; |
|
|
best_price = Some(price); |
|
|
|
|
|
eprintln!("Finished randomization, price: {}, new best, printing", price); |
|
|
|
|
|
println!("New best!"); |
|
|
|
|
|
println!("Price {}, seed {}", price, seed); |
|
|
|
|
|
print_houses(&layout.houses()); |
|
|
|
|
|
println!(); |
|
|
|
|
|
} else { |
|
|
|
|
|
eprintln!("Finished randomization, price: {}, printing", price); |
|
|
|
|
|
println!("Price {}, seed {}", price, seed); |
|
|
|
|
|
print_houses(&layout.houses()); |
|
|
|
|
|
println!(); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
improved |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub enum RectangleSearchError { |
|
|
pub enum RectangleSearchError { |
|
|
UselessHouse |
|
|
Useless, |
|
|
|
|
|
Unsatisfiable |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn get_valid_move_rectangle_multiple(layout: &HouseLayout, houses: &Vec<House>) -> Result<Rectangle, RectangleSearchError> { |
|
|
|
|
|
// This is a generalization of get_valid_move_rectangle, it's basically the same thing,
|
|
|
|
|
|
// just with a dynamic rectangles_containing_count
|
|
|
|
|
|
|
|
|
|
|
|
// We first establish a bounding box for an that has to be covered if all houses are removed.
|
|
|
|
|
|
let mut covered_rect: Option<Rectangle> = None; |
|
|
|
|
|
for house in houses { |
|
|
|
|
|
let range_rect = house.range_rectangle(); |
|
|
|
|
|
for y in range_rect.top..=range_rect.bottom { |
|
|
|
|
|
for x in range_rect.left..=range_rect.right { |
|
|
|
|
|
// We count how many rectangles of houses contain this xy position.
|
|
|
|
|
|
let mut rectangles_containing_count = 0; |
|
|
|
|
|
for house in houses { |
|
|
|
|
|
let rect = house.range_rectangle(); |
|
|
|
|
|
if rect.is_inside(x, y) { |
|
|
|
|
|
rectangles_containing_count += 1; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// If this house is covered by the exact amount of rectangles,
|
|
|
|
|
|
// then removing all input houses would uncover this position.
|
|
|
|
|
|
// It cannot be less than the rectangle count, and more means there
|
|
|
|
|
|
// is another house covering it as well.
|
|
|
|
|
|
if layout.cover_count_xy(x, y) == rectangles_containing_count && layout.city.is_house_xy(x, y) { |
|
|
|
|
|
if let Some(cover) = &mut covered_rect { |
|
|
|
|
|
cover.left = cover.left.min(x); |
|
|
|
|
|
cover.right = cover.right.max(x); |
|
|
|
|
|
cover.top = cover.top.min(y); |
|
|
|
|
|
cover.bottom = cover.bottom.max(y); |
|
|
|
|
|
} else { |
|
|
|
|
|
covered_rect = Some(Rectangle { left: x, right: x, top: y, bottom: y }); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if covered_rect.is_none() { |
|
|
|
|
|
// Unnecessary set of houses.
|
|
|
|
|
|
return Err(RectangleSearchError::Useless); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let covered_rect = covered_rect.unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
let height_margin = HOUSE_RANGE as i32 - covered_rect.height() as i32; |
|
|
|
|
|
let width_margin = HOUSE_RANGE as i32 - covered_rect.width() as i32; |
|
|
|
|
|
|
|
|
|
|
|
let top = (covered_rect.top as i32 - height_margin).max(0) as usize; |
|
|
|
|
|
let left = (covered_rect.left as i32 - width_margin).max(0) as usize; |
|
|
|
|
|
let bottom = (covered_rect.bottom + height_margin as usize).min(SIZE - 1); |
|
|
|
|
|
let right = (covered_rect.right + width_margin as usize).min(SIZE - 1); |
|
|
|
|
|
|
|
|
|
|
|
if top > bottom || left > right { |
|
|
|
|
|
// Unsatisfiable rectangle by one house
|
|
|
|
|
|
return Err(RectangleSearchError::Unsatisfiable); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Ok(Rectangle { left, right, top, bottom }) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectangle, RectangleSearchError> { |
|
|
pub fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectangle, RectangleSearchError> { |
|
@ -292,7 +491,7 @@ pub fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Re |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if covered_rect.is_none() { |
|
|
if covered_rect.is_none() { |
|
|
return Err(RectangleSearchError::UselessHouse) |
|
|
return Err(RectangleSearchError::Useless) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let covered_rect = covered_rect.unwrap(); |
|
|
let covered_rect = covered_rect.unwrap(); |
|
|