diff --git a/Cargo.toml b/Cargo.toml
index decc041..2071832 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,3 +21,7 @@ path = "src/main.rs"
 [[bin]]
 name = "import-logs"
 path = "src/import-logs.rs"
+
+[[bin]]
+name = "combine"
+path = "src/combine-layouts.rs"
diff --git a/src/city.rs b/src/city.rs
index 964a80f..ba783a3 100644
--- a/src/city.rs
+++ b/src/city.rs
@@ -188,7 +188,7 @@ pub fn get_price(city: &City, houses: &Vec<House>) -> u32 {
     price
 }
 
-fn is_valid(city: &City, houses: &Vec<House>) -> Option<u32> {
+pub fn is_valid(city: &City, houses: &Vec<House>) -> Option<u32> {
     let mut reachable = vec![false; SIZE * SIZE];
     let mut price = 0u32;
 
diff --git a/src/combine-layouts.rs b/src/combine-layouts.rs
new file mode 100644
index 0000000..773ed0f
--- /dev/null
+++ b/src/combine-layouts.rs
@@ -0,0 +1,28 @@
+use db::LayoutDB;
+use city::City;
+use itertools::Itertools;
+
+mod city;
+mod db;
+mod combine;
+
+fn main() {
+    let mut db = LayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB");
+    eprintln!("Loaded the DB, {} stored layouts", db.layouts().len());
+
+    let city = City::read_from_file("01.in");
+    eprintln!("Loaded the city file, {} houses", city.get_house_count());
+
+    let layouts = db.layouts();
+    let sorted: Vec<_> = layouts.iter().sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))).collect();
+    let first = sorted[1];
+    let second = sorted[0];
+    eprintln!("Combining layouts (ID {}, price {}) and (ID {}, price {})",
+              first.id(),
+              city::get_price(&city, first.houses()),
+              second.id(),
+              city::get_price(&city, second.houses()),
+    );
+
+    combine::try_combine(&city, first.houses(), second.houses());
+}
\ No newline at end of file
diff --git a/src/combine.rs b/src/combine.rs
new file mode 100644
index 0000000..3c0781c
--- /dev/null
+++ b/src/combine.rs
@@ -0,0 +1,163 @@
+use crate::city;
+use crate::city::{City, House, SIZE, HOUSE_RANGE};
+use itertools::Itertools;
+use std::collections::VecDeque;
+
+pub fn try_combine(city: &City, layout1: &Vec<House>, layout2: &Vec<House>) {
+    // Sorted in reverse so we can remove from the end
+    let mut houses1_sorted: Vec<House> = layout1.iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect();
+    let mut houses2_sorted: Vec<House> = layout2.iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect();
+
+    // TODO: We may want to maintain K left sides and K right sides to compare K^2 layouts at once at each x
+
+    // houses1 is left, houses2 is right
+
+    let mut left = LeftLine::new();
+    let mut right = RightLine::new();
+
+    // Make sure that we include all houses initially
+    while let Some(house) = houses2_sorted.pop() {
+        right.add_house(house);
+    }
+
+    // x is the last left coordinate, x+1 is right
+    for x in 0..SIZE {
+        // Update the lines
+        while let Some(house) = houses1_sorted.last() {
+            if house.x == x {
+                left.add_house(*house);
+                houses1_sorted.pop();
+            } else {
+                break;
+            }
+        }
+        right.remove_houses(x);
+
+        // 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;
+        }
+
+        if is_compatible(city, &left, &right) {
+            eprintln!("Compatible on X {}", x);
+            let houses: Vec<_> = layout1.iter().filter(|h| h.x <= x).chain(layout2.iter().filter(|h| h.x > x)).map(|h| *h).collect();
+            eprintln!("Price {}", city::get_price(&city, &houses));
+            //if let Some(price) = city::is_valid(&city, &houses) {
+            //    eprintln!("Merge valid with price {}", price)
+            //} else {
+            //    eprintln!("Merge actually invalid, printing invalid merge");
+            //    println!("{}", houses.len());
+            //    for house in houses {
+            //        println!("{} {}", house.y, house.x);
+            //    }
+            //}
+        } else {
+            eprintln!("Incompatible on X {}", x);
+        }
+    }
+}
+
+fn is_compatible(city: &City, left: &LeftLine, right: &RightLine) -> bool {
+    for y in 0..SIZE {
+        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) {
+                // This is an uncovered house
+                eprintln!("House ({},{}) in uncovered range [{},{}]", x, y, max_left_covered_x+1, min_right_covered_x-1);
+                return false;
+            }
+        }
+    }
+
+    true
+}
+
+struct LeftLine {
+    covers: Vec<usize>
+}
+
+struct RightLine {
+    covers: Vec<usize>,
+    houses: VecDeque<House>
+}
+
+impl LeftLine {
+    pub fn new() -> Self {
+        // XXX: Careful, default of 0 includes covering first vertical line
+        let covers = vec![0; SIZE];
+        LeftLine {covers}
+    }
+
+    pub fn add_house(&mut self, house: House) {
+        let range_rect = house.range_rectangle();
+        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);
+        }
+    }
+
+    pub fn get_max_covered_x(&self, y: usize) -> usize {
+        self.covers[y]
+    }
+}
+
+impl RightLine {
+    pub fn new() -> Self {
+        let covers = vec![usize::MAX; SIZE];
+        let houses = VecDeque::new();
+        RightLine {covers, houses}
+    }
+
+    pub fn add_house(&mut self, house: House) {
+        // Added houses have to always be ordered by x
+        eprintln!("Added house ({},{}) to right line", house.x, house.y);
+        let range_rect = house.range_rectangle();
+        for y in range_rect.top..=range_rect.bottom {
+            self.covers[y] = self.covers[y].min(range_rect.left);
+        }
+
+        self.houses.push_back(house);
+    }
+
+    pub fn remove_houses(&mut self, x: usize) {
+        // 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();
+
+                // 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();
+                    // TODO: Verify this intersection is correct
+                    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);
+                    }
+                }
+            } else {
+                break;
+            }
+        }
+    }
+
+    pub fn get_min_covered_x(&self, y: usize) -> usize {
+        self.covers[y].min(SIZE)
+    }
+}
diff --git a/src/db.rs b/src/db.rs
index b59bf37..f012eb1 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -16,6 +16,10 @@ impl SavedLayout {
     pub fn houses(&self) -> &Vec<House> {
         &self.houses
     }
+
+    pub fn id(&self) -> usize {
+        self.id
+    }
 }
 
 impl LayoutDB {
diff --git a/src/main.rs b/src/main.rs
index d857178..a0c1c65 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,7 +21,7 @@ fn main() {
 
     const MIN_WEIGHT_SCORE: f64 = 600000.;
     const MAX_WEIGHT_SCORE: f64 = 700000.;
-    const DB_CHOICE_PROBABILITY: f64 = 0.9;
+    const DB_CHOICE_PROBABILITY: f64 = 0.99;
 
     let mut best_price: Option<u32> = None;