Řešení KSP úlohy 33-3-4 Obsazování území https://ksp.mff.cuni.cz/h/ulohy/33/zadani3.html#task-33-3-4
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
12 KiB

use crate::city;
use crate::db::{SqliteLayoutDB, SavedLayout, MergeLowerBound, LayoutDB};
use crate::city::{City, House};
use itertools::Itertools;
use itertools::iproduct;
use std::collections::{VecDeque, HashMap};
use std::collections::vec_deque::Iter;
struct LeftState<'a> {
layout: &'a SavedLayout,
sorted_houses: Vec<House>,
line: LeftLine<'a>
}
struct RightState<'a> {
layout: &'a SavedLayout,
line: RightLine<'a>
}
pub struct CompatibilityCache {
map: HashMap<(usize, usize, usize, usize, bool), bool>
}
impl CompatibilityCache {
pub fn new() -> CompatibilityCache {
CompatibilityCache {
map: HashMap::new()
}
}
pub fn is_compatible(&self, left_layout: &SavedLayout, right_layout: &SavedLayout, left_update_index: usize, right_update_index: usize, y_axis: bool) -> Option<&bool> {
self.map.get(&(left_layout.id(), right_layout.id(), left_update_index, right_update_index, y_axis))
}
pub fn set_compatible(&mut self, left_layout: &SavedLayout, right_layout: &SavedLayout, left_update_index: usize, right_update_index: usize, y_axis: bool, compatible: bool) {
self.map.insert((left_layout.id(), right_layout.id(), left_update_index, right_update_index, y_axis), compatible);
}
}
pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Vec<SavedLayout>, right_layouts: &Vec<SavedLayout>, db: &mut TDB, cache: &mut CompatibilityCache, transposed: bool) -> bool {
let mut best_price = left_layouts.iter().chain(right_layouts.iter())
.map(|layout| city::get_price(&city, layout.houses()))
.min();
let mut improved = false;
let mut lefts = Vec::new();
let mut rights = Vec::new();
for left_layout in left_layouts {
// We make sure that there is at least one other layout we want to compare this one with
// to avoid unnecessary house updates
let mut needed = false;
for right_layout in right_layouts {
if let Some(bound) = db.get_merge_lower_bound(left_layout, right_layout, transposed) {
if bound < best_price.expect("No best price set while lower bounds exist") {
needed = true;
break;
}
} else {
needed = true;
break;
}
}
if !needed {
continue;
}
// Sorted in reverse so we can remove from the end
let sorted_houses: Vec<_> = left_layout.houses().iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect();
lefts.push(LeftState {
layout: &left_layout,
sorted_houses,
line: LeftLine::new(&city)
});
}
for right_layout in right_layouts {
// We make sure that there is at least one other layout we want to compare this one with
// to avoid unnecessary house updates
let mut needed = false;
for left_layout in left_layouts {
if let Some(bound) = db.get_merge_lower_bound(left_layout, right_layout, transposed) {
if bound < best_price.expect("No best price set while lower bounds exist") {
needed = true;
}
} else {
needed = true;
}
}
if !needed {
continue;
}
let mut sorted_houses: Vec<_> = right_layout.houses().iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect();
let mut line = RightLine::new(&city);
// Make sure that we include all houses initially
while let Some(house) = sorted_houses.pop() {
line.add_house(house, &city);
}
rights.push(RightState {
layout: right_layout,
line
});
}
let axis = if transposed { "y" } else { "x" };
// x is the last left coordinate, x+1 is right
for x in 0..city.width() {
eprintln!("Starting {} {}", axis, x);
// Update the lines
for left in lefts.iter_mut() {
while let Some(house) = left.sorted_houses.last() {
if house.x == x {
left.line.add_house(*house, &city);
left.sorted_houses.pop();
} else {
break;
}
}
}
for right_line in rights.iter_mut() {
right_line.line.remove_houses(x, &city);
}
// Check compatibility of lines
if x == 0 {
// Cannot check this due to limitations in the implementation of LeftLine,
// it wouldn't be very interesting anyway.
continue;
}
let pairs = iproduct!(lefts.iter().enumerate(), rights.iter().enumerate())
.filter(|((left_i, _), (right_i, _))| left_i != right_i)
.filter(|((_, left), (_, right))| {
if let Some(bound) = db.get_merge_lower_bound(left.layout, right.layout, transposed) {
if bound >= best_price.expect("No best price set while lower bounds exist") {
return false;
}
}
return true;
})
.map(|((left_i, left), (right_i, right))| (left, right, left.line.price + right.line.price, left_i, right_i))
.sorted_by(|(_, _, price1, _, _), (_, _, price2, _, _)| price1.cmp(&price2));
let mut compatibles = 0;
let mut incompatibles = 0;
let mut cache_hits = 0;
for (left, right, price, left_i, right_i) in pairs {
if let Some(min_price) = best_price {
if price >= min_price {
break;
}
}
let compatible = match cache.is_compatible(left.layout, right.layout, left.line.last_update_x, right.line.last_update_x, transposed) {
None => {
let compatible = is_compatible(city, &left.line, &right.line);
cache.set_compatible(left.layout, right.layout, left.line.last_update_x, right.line.last_update_x, transposed, compatible);
compatible
}
Some(compatible) => {
cache_hits += 1;
*compatible
}
};
if compatible {
if best_price.is_none() || price < best_price.unwrap() {
best_price = Some(price);
eprintln!("{} - new best score, cut on {} {}, left {} - right {}, printing", price, axis, x, left_i, right_i);
println!("{} - new best score, cut on {} {}, left {} - right {}", price, axis, x, left_i, right_i);
let mut new_houses: Vec<_> = left.line.houses().copied().chain(right.line.houses().copied()).collect();
if transposed {
new_houses = transpose_layout(&new_houses);
}
println!("{}", new_houses.len());
for house in &new_houses {
println!("{} {}", house.y, house.x);
}
// We only add best results to avoid overfilling the database with similar layouts
db.add_layout(&new_houses, false);
improved = true;
}
compatibles += 1;
// All other pairs would be more expensive
break;
} else {
incompatibles += 1;
}
}
eprintln!("{} incompatibles checked before {} compatible ({} cache hits)", incompatibles, compatibles, cache_hits);
}
if let Some(lowest_price) = best_price {
let mut new_bounds = Vec::new();
for left_layout in left_layouts {
for right_layout in right_layouts {
new_bounds.push(MergeLowerBound::new(left_layout.id(), right_layout.id(), transposed, lowest_price));
}
}
db.add_merge_lower_bounds(new_bounds);
}
improved
}
fn is_compatible(city: &City, left: &LeftLine, right: &RightLine) -> bool {
for y in 0..city.height() {
let max_left_covered_x = left.get_max_covered_x(y);
let min_right_covered_x = right.get_min_covered_x(y);
// This range will often be empty
for x in (max_left_covered_x + 1)..min_right_covered_x {
if city.is_house_xy(x, y) {
return false;
}
}
}
true
}
struct LeftLine<'a> {
covers: Vec<usize>,
houses: Vec<House>,
price: u32,
last_update_x: usize,
city: &'a City
}
struct RightLine<'a> {
covers: Vec<usize>,
houses: VecDeque<House>,
price: u32,
last_update_x: usize,
city: &'a City
}
impl<'a> LeftLine<'a> {
pub fn new(city: &'a City) -> Self {
// XXX: Careful, default of 0 includes covering first vertical line
let covers = vec![0; city.height()];
let houses = Vec::new();
LeftLine { covers, houses, price: 0, last_update_x: 0, city }
}
pub fn add_house(&mut self, house: House, city: &City) {
let range_rect = house.range_rectangle(city);
for y in range_rect.top..=range_rect.bottom {
// Should always be the max variant
self.covers[y] = self.covers[y].max(range_rect.right);
}
self.price += city.get_price(house) as u32;
self.houses.push(house);
self.last_update_x = house.x;
}
pub fn get_max_covered_x(&self, y: usize) -> usize {
self.covers[y]
}
pub fn get_side_price(&self) -> u32 {
self.price
}
pub fn houses(&self) -> std::slice::Iter<'_, House> {
self.houses.iter()
}
}
impl<'a> RightLine<'a> {
pub fn new(city: &'a City) -> Self {
let covers = vec![usize::MAX; city.height()];
let houses = VecDeque::new();
RightLine { covers, houses, price: 0, last_update_x: 0, city }
}
pub fn add_house(&mut self, house: House, city: &City) {
// Added houses have to always be ordered by x
let range_rect = house.range_rectangle(city);
for y in range_rect.top..=range_rect.bottom {
self.covers[y] = self.covers[y].min(range_rect.left);
}
self.houses.push_back(house);
self.price += city.get_price(house) as u32;
self.last_update_x = house.x;
}
pub fn remove_houses(&mut self, x: usize, city: &City) {
// Has to be called with x, x+1, x+2...
while let Some(house) = self.houses.front() {
if house.x == x {
let removed_house = self.houses.pop_front().unwrap();
let removed_rect = removed_house.range_rectangle(city);
// Remove the now-outdated distances around the removed house
for y in removed_rect.top..=removed_rect.bottom {
self.covers[y] = usize::MAX;
}
// Update distances around the removed house if the area of any houses
// intersects the removed area
for house in &self.houses {
let house_rect = house.range_rectangle(city);
let y_intersection = if removed_house.y < house.y {
house_rect.top..=removed_rect.bottom
} else {
removed_rect.top..=house_rect.bottom
};
for y in y_intersection {
self.covers[y] = self.covers[y].min(house_rect.left);
}
}
self.price -= city.get_price(removed_house) as u32;
self.last_update_x = removed_house.x;
} else {
break;
}
}
}
pub fn get_min_covered_x(&self, y: usize) -> usize {
self.covers[y].min(self.city.width())
}
pub fn get_side_price(&self) -> u32 {
self.price
}
pub fn houses(&self) -> Iter<'_, House> {
self.houses.iter()
}
}
pub fn transpose_layout(houses: &Vec<House>) -> Vec<House> {
let mut transposed = Vec::new();
for house in houses {
transposed.push(House::new(house.y, house.x));
}
transposed
}