diff --git a/src/bin/d16p1.rs b/src/bin/d16p1.rs
new file mode 100644
index 0000000..a5d72ba
--- /dev/null
+++ b/src/bin/d16p1.rs
@@ -0,0 +1,58 @@
+use std::{
+    sync::{
+        atomic::{AtomicUsize, Ordering},
+        Arc, Mutex,
+    },
+    thread,
+};
+
+use aoc22::{day16, util};
+
+pub fn main() {
+    let valves = day16::parse_valves(&util::parse_input());
+    let dists = day16::calc_dists(&valves);
+    let state = day16::State::new(Arc::new(valves), Arc::new(dists));
+
+    let possible_states = Arc::new(Mutex::new(Vec::new()));
+    possible_states.lock().unwrap().push(state);
+
+    let lower_bound = Arc::new(AtomicUsize::new(0));
+
+    let mut handles = Vec::new();
+
+    for _ in 0..16 {
+        let s = possible_states.clone();
+        let l = lower_bound.clone();
+        handles.push(thread::spawn(move || check_states(s, l)));
+    }
+
+    for handle in handles {
+        handle.join().unwrap();
+    }
+
+    println!(
+        "Most pressure released is {}",
+        lower_bound.load(Ordering::Relaxed)
+    );
+}
+
+pub fn check_states(possible_states: Arc<Mutex<Vec<day16::State>>>, lower_bound: Arc<AtomicUsize>) {
+    loop {
+        let state = { possible_states.lock().unwrap().pop() };
+        if state.is_none() {
+            break;
+        }
+        let state = state.unwrap();
+        if state.finished() {
+            let score = state.lower_bound();
+            lower_bound.fetch_max(score, Ordering::Relaxed);
+        } else {
+            let x = state.possible_actions();
+            for action in x {
+                if action.upper_bound() > lower_bound.load(Ordering::Relaxed) {
+                    possible_states.lock().unwrap().push(action);
+                }
+            }
+        }
+    }
+}
diff --git a/src/day16.rs b/src/day16.rs
new file mode 100644
index 0000000..b13bf3f
--- /dev/null
+++ b/src/day16.rs
@@ -0,0 +1,199 @@
+use std::{
+    cmp::Ordering,
+    collections::{HashMap, HashSet, VecDeque},
+    sync::Arc,
+};
+
+use itertools::Itertools;
+use regex::Regex;
+
+const TOTAL_TIME: usize = 30;
+
+#[derive(Debug)]
+pub struct Valve {
+    pub name: String,
+    pub flow_rate: usize,
+    pub tunnels: Vec<String>,
+}
+
+pub fn parse_valves(input: &String) -> HashMap<String, Valve> {
+    let mut valves = HashMap::new();
+
+    let re =
+        Regex::new(r"^Valve (\w+) has flow rate=(\d+); tunnels? leads? to valves? (.*)$").unwrap();
+    for line in input.lines() {
+        let captures = re
+            .captures(line)
+            .expect(&format!("No captures for {}", line));
+        let name = captures.get(1).unwrap().as_str().to_owned();
+        let flow_rate = captures.get(2).unwrap().as_str().parse().unwrap();
+        let tunnels = captures
+            .get(3)
+            .unwrap()
+            .as_str()
+            .split(", ")
+            .map(|s| s.to_owned())
+            .collect();
+        let valve = Valve {
+            name: name.clone(),
+            flow_rate,
+            tunnels,
+        };
+        valves.insert(name, valve);
+    }
+
+    valves
+}
+
+pub fn calc_dists(valves: &HashMap<String, Valve>) -> HashMap<(String, String), usize> {
+    let mut result = HashMap::new();
+    let dists_orig: HashMap<String, usize> = valves.keys().map(|name| (name.clone(), 0)).collect();
+
+    for name in valves.keys() {
+        let mut dists = dists_orig.clone();
+        let mut next = VecDeque::new();
+        next.push_back(name);
+        while let Some(n) = next.pop_front() {
+            let d_next = dists[n] + 1;
+            for tunnel in &valves[n].tunnels {
+                let d_tunnel = dists.get_mut(tunnel).unwrap();
+                if *d_tunnel > d_next {
+                    *d_tunnel = d_next;
+                    next.push_back(tunnel);
+                }
+            }
+        }
+
+        for n in valves.keys() {
+            result.insert((name.clone(), n.clone()), dists[n]);
+        }
+    }
+
+    result
+}
+
+#[derive(Debug, Clone)]
+pub struct State {
+    pub valves: Arc<HashMap<String, Valve>>,
+    pub dists: Arc<HashMap<(String, String), usize>>,
+    pub opened: HashMap<String, bool>,
+    pub interesting: HashSet<String>,
+    pub location: String,
+    pub flow_rate: usize,
+    pub time_remaining: usize,
+    pub pressure_released: usize,
+}
+
+impl State {
+    pub fn new(
+        valves: Arc<HashMap<String, Valve>>,
+        dists: Arc<HashMap<(String, String), usize>>,
+    ) -> State {
+        let opened = valves.iter().map(|(k, _)| (k.clone(), false)).collect();
+
+        let interesting = valves
+            .iter()
+            .filter_map(|(name, valve)| {
+                if valve.flow_rate > 0 {
+                    Some(name.clone())
+                } else {
+                    None
+                }
+            })
+            .collect();
+
+        State {
+            valves,
+            dists,
+            opened,
+            interesting,
+            location: "AA".to_owned(),
+            flow_rate: 0,
+            time_remaining: TOTAL_TIME,
+            pressure_released: 0,
+        }
+    }
+
+    pub fn finished(&self) -> bool {
+        self.time_remaining == 0 || self.interesting.is_empty()
+    }
+
+    pub fn lower_bound(&self) -> usize {
+        self.pressure_released + self.flow_rate * self.time_remaining
+    }
+
+    pub fn upper_bound(&self) -> usize {
+        let additional_flow: isize = self
+            .opened
+            .iter()
+            .filter_map(|(k, opened)| {
+                if *opened {
+                    None
+                } else {
+                    let dist = self.dists[&(self.location.clone(), k.clone())];
+                    if dist + 1 > self.time_remaining {
+                        None
+                    } else {
+                        let flow = self.valves[k].flow_rate;
+                        Some(-((flow * (self.time_remaining - dist - 1)) as isize))
+                    }
+                }
+            })
+            // .sorted()
+            // .rev()
+            // .take(self.time_remaining)
+            .sum();
+        let additional_flow = (-additional_flow) as usize;
+
+        self.lower_bound() + additional_flow
+    }
+
+    pub fn possible_actions(&self) -> Vec<State> {
+        assert!(!self.finished());
+
+        let mut result = Vec::new();
+
+        let loc = &self.location;
+
+        if !self.opened[loc] {
+            let mut open = self.clone();
+            *open.opened.get_mut(loc).unwrap() = true;
+            // First increase pressure released, then the flow rate
+            open.pressure_released += open.flow_rate;
+            open.flow_rate += self.valves[loc].flow_rate;
+            open.time_remaining -= 1;
+            open.interesting.remove(loc);
+            result.push(open);
+        }
+
+        for tunnel in &self.valves[loc].tunnels {
+            let mut moved = self.clone();
+            moved.location = tunnel.clone();
+            moved.pressure_released += moved.flow_rate;
+            moved.time_remaining -= 1;
+            result.push(moved);
+        }
+
+        result
+    }
+}
+
+impl Ord for State {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.upper_bound().cmp(&other.upper_bound())
+    }
+}
+
+impl PartialOrd for State {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl PartialEq for State {
+    fn eq(&self, other: &Self) -> bool {
+        self.cmp(other) == Ordering::Equal
+    }
+}
+
+impl Eq for State {}
diff --git a/src/lib.rs b/src/lib.rs
index 0a502f3..f75f5f3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,6 +5,7 @@ pub mod day12;
 pub mod day13;
 pub mod day14;
 pub mod day15;
+pub mod day16;
 pub mod day2;
 pub mod day3;
 pub mod day4;