#![allow(non_snake_case)] use std::fmt; use std::ops::Add; use std::collections::BinaryHeap; use crate::rand::Rng; const N: usize = 50; const M: usize = 20; struct Diff { dx: i64, dy: i64, } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] struct Pos { x: i64, y: i64, } impl Add for Pos { type Output = Pos; fn add(self, d: Diff) -> Pos { Pos {x: self.x + d.dx, y: self.y + d.dy} } } impl Add<&Diff> for Pos { type Output = Pos; fn add(self, d: &Diff) -> Pos { Pos {x: self.x + d.dx, y: self.y + d.dy} } } impl Pos { fn is_valid(&self) -> bool { self.x >= 0 && self.y >= 0 && (self.x as usize) < N && (self.y as usize) < N } } struct Input { A: Vec>, costs: [usize; M], offsets: [Vec; M], } fn read_input() -> Input { let mut line = String::new(); std::io::stdin().read_line(&mut line).unwrap(); let nums: Vec<&str> = line.split_whitespace().collect(); assert!(nums.len() == 2); assert!(nums[0].parse::() == Ok(N)); assert!(nums[1].parse::() == Ok(M)); let mut A = Vec::new(); for _ in 0..N { line.clear(); std::io::stdin().read_line(&mut line).unwrap(); A.push(line.chars().collect()); } let mut costs = [0; M]; let mut offsets: [Vec; M] = Default::default(); for j in 0..M { line.clear(); std::io::stdin().read_line(&mut line).unwrap(); let nums: Vec<&str> = line.split_whitespace().collect(); assert!(nums.len() == 2); let C: usize = nums[0].parse().unwrap(); let L: usize = nums[1].parse().unwrap(); costs[j] = C; for _ in 0..L { line.clear(); std::io::stdin().read_line(&mut line).unwrap(); let nums: Vec<&str> = line.split_whitespace().collect(); assert!(nums.len() == 2); let dx: i64 = nums[0].parse().unwrap(); let dy: i64 = nums[1].parse().unwrap(); offsets[j].push(Diff{ dx, dy }) } } Input { A, costs, offsets } } #[derive(Clone, Copy)] enum Dir { L, R, U, D } const ALL_DIRS: [Dir; 4] = [Dir::L, Dir:: R, Dir:: U, Dir::D]; impl fmt::Display for Dir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let c = match self { Dir::L => 'L', Dir::R => 'R', Dir::U => 'U', Dir::D => 'D' }; write!(f, "{}", c) } } impl Dir { fn to_diff(self) -> Diff { match self { Dir::L => Diff{dx: 0, dy: -1}, Dir::R => Diff{dx: 0, dy: 1}, Dir::U => Diff{dx: -1, dy: 0}, Dir::D => Diff{dx: 1, dy: 0}, } } } enum Move { Buy { bomb: usize, shop_pos: Pos }, Use { bomb: usize, pos: Pos }, } enum AtomicMove { Walk(Dir), Buy(usize), Use(usize), } fn list_bomb_use(input: &Input) -> Vec<(usize, Pos)> { let mut A = input.A.clone(); let mut used_bombs = Vec::new(); let mut ub = [[[N * N; N]; N]; M]; // 枝刈り用 loop { let mut max_eval = 0; let mut cand = (0, 0, 0); let calc_eval = |count: usize, i: usize| { 1_000_000_000 * count / input.costs[i] }; for i in 0..M { for x in 0..N as i64 { for y in 0..N as i64 { if calc_eval(ub[i][x as usize][y as usize], i) <= max_eval { continue } let p = Pos{x, y}; let mut count = 0; for d in &input.offsets[i] { let q = p + d; if q.is_valid() { let x = q.x as usize; let y = q.y as usize; if A[x][y] != '.' { count += 1 } } } ub[i][x as usize][y as usize] = ub[i][x as usize][y as usize].min(count); let eval = calc_eval(count, i); if eval > max_eval { max_eval = eval; cand = (i, x, y) } } } } if max_eval == 0 { break } else { let (i, x, y) = cand; let p = Pos{x, y}; used_bombs.push((i, p)); for d in &input.offsets[i] { let q = p + d; if q.is_valid() { let x = q.x as usize; let y = q.y as usize; A[x][y] = '.'; } } } } used_bombs } fn L1(a: Pos, b: Pos) -> usize { ((a.x - b.x).abs() + (a.y - b.y).abs()) as usize } fn shortest_path(A: &Vec>, start: Pos, goal: Pos) -> Vec { let mut pq = BinaryHeap::new(); pq.push((0i64, goal)); let mut distance = vec![vec![1i64 << 50; N]; N]; let mut next_dir = vec![vec![None::; N]; N]; distance[goal.x as usize][goal.y as usize] = 0; while !pq.is_empty() { let (md, p) = pq.pop().unwrap(); let d = -md; if distance[p.x as usize][p.y as usize] < d { continue } let weight = if A[p.x as usize][p.y as usize] == '.' { 1 } else { 2 }; for dir in ALL_DIRS { let diff = dir.to_diff(); // 逆向きに見ているので引く let x1 = p.x - diff.dx; let y1 = p.y - diff.dy; let p1 = Pos{x: x1, y: y1}; let new_dist = distance[p.x as usize][p.y as usize] + weight; if p1.is_valid() && distance[x1 as usize][y1 as usize] > new_dist { distance[x1 as usize][y1 as usize] = new_dist; next_dir[x1 as usize][y1 as usize] = Some(dir); pq.push((-new_dist, p1)); } } } let mut p = start; let mut result = Vec::new(); while p != goal { let dir = next_dir[p.x as usize][p.y as usize].unwrap(); result.push(dir); p = p + dir.to_diff(); } result } fn split_uselist(input: &Input, sol: &Vec>, seed: u32, timer: &Timer, tl: u64) -> (Vec>, u64) { let all_shops: Vec = { let mut result = Vec::new(); for x in 0..N { for y in 0..N { if input.A[x][y] == '@' { result.push(Pos{x: x as i64, y: y as i64}) } } } result }; let calc_cost = |sol: &Vec>| -> u64 { let mut p = Pos{x: 0, y: 0}; let mut A = input.A.clone(); let mut cost: u64 = 0; for use_list in sol { assert!(!use_list.is_empty()); let q = use_list[0].1; let weight = ((use_list.len() + 1) * (use_list.len() + 1)) as u64; let best_shop: Option<&Pos> = { all_shops.iter() .filter(|p| A[p.x as usize][p.y as usize] == '@') .min_by_key(|s| L1(p, **s) as u64 + weight * L1(**s, q) as u64) }; if best_shop.is_none() { return 1u64 << 60 } assert!(best_shop.is_some()); let s = *best_shop.unwrap(); cost += L1(p, s) as u64; p = s; for i in 0..use_list.len() { let weight = ((use_list.len() - i + 1) * (use_list.len() - i + 1)) as u64; cost += weight * L1(p, use_list[i].1) as u64; p = use_list[i].1; for diff in &input.offsets[use_list[i].0] { let q = p + diff; if q.is_valid() { let Pos{x, y} = q; A[x as usize][y as usize] = '.'; } } } } cost }; let mut best_sol = sol.clone(); let mut best_cost = calc_cost(&best_sol); let mut cur_sol = sol.clone(); let mut cur_cost = best_cost; let mut rng = rand::Rng::new(seed); let accept = |new_cost: u64, cur_cost: u64, progress: f64, rng: &mut Rng| { let start_temp = 20.0; let end_temp = 1.0; let temp = start_temp + progress * (end_temp - start_temp); if new_cost > cur_cost + (temp * 20.0) as u64 { return false } else if new_cost <= cur_cost { true } else { let prob = (-(cur_cost.abs_diff(new_cost) as f64) / temp).exp(); rng.rand_bool(prob) } }; #[allow(dead_code)] enum Neighbor { Split(usize, usize), Merge(usize), Rev1(usize), Swap(usize, usize), Remove(usize), Replace(usize, usize, usize, Pos), } let mut acc = [0; 6]; let start_time = timer.get_elapsed_time(); let mut iter = 0; while timer.get_elapsed_time() < tl { let mut tmp_sol = best_sol.clone(); let nbr = loop { match rng.uniform_u32(0, 6) { 0 | 1 => { let i = rng.uniform_u32(0, tmp_sol.len() as u32) as usize; if tmp_sol[i].len() > 1 { let j = rng.uniform_u32(1, tmp_sol[i].len() as u32) as usize; break Neighbor::Split(i, j) } } 2 => { if tmp_sol.len() > 1 { let i = rng.uniform_u32(0, tmp_sol.len() as u32 - 1) as usize; break Neighbor::Merge(i) } } 3 => { let i = rng.uniform_u32(0, tmp_sol.len() as u32) as usize; if tmp_sol[i].len() > 1 { break Neighbor::Rev1(i) } } // 4 => { // let mut flat_sol = Vec::new(); // for l in &tmp_sol { flat_sol.append(&mut l.clone()) } // match find_alternative(&input, &flat_sol) { // None => (), // Some((j, None)) => break Neighbor::Remove(j), // Some((j, Some((i, p)))) => { // let mut a = 0; // let mut res = None; // for k in 0..tmp_sol.len() { // assert!(a <= j); // if j < a + tmp_sol[k].len() { // res = Some(Neighbor::Replace(k, j - a, i, p)); // break // } else { // a += tmp_sol[k].len() // } // } // if let Some(n) = res { break n } // } // } // } _ => { if tmp_sol.len() > 1 { let i = rng.uniform_u32(0, tmp_sol.len() as u32) as usize; let j = rng.uniform_u32(0, tmp_sol.len() as u32) as usize; if i != j { break Neighbor::Swap(i, j) } } } } }; match nbr { Neighbor::Split(i, j) => { tmp_sol.insert(i + 1, tmp_sol[i][j..].iter().map(|&x| x).collect()); tmp_sol[i].truncate(j); } Neighbor::Merge(i) => { assert!(i + 1 < tmp_sol.len()); let mut tail = tmp_sol.remove(i + 1); tmp_sol[i].append(&mut tail); } Neighbor::Rev1(i) => { tmp_sol[i].reverse(); } Neighbor::Swap(i, j) => { tmp_sol.swap(i, j); } Neighbor::Remove(j) => { tmp_sol.remove(j); } Neighbor::Replace(k, j, i, p) => { tmp_sol[k][j] = (i, p) } } let tmp_cost = calc_cost(&tmp_sol); let progress = (timer.get_elapsed_time() - start_time) as f64 / (tl - start_time) as f64; iter += 1; if accept(tmp_cost, cur_cost, progress, &mut rng) { cur_sol = tmp_sol; cur_cost = tmp_cost; match nbr { Neighbor::Split(_, _) => acc[0] += 1, Neighbor::Merge(_) => acc[1] += 1, Neighbor::Rev1(_) => acc[2] += 1, Neighbor::Swap(_, _) => acc[3] += 1, Neighbor::Remove(_) => acc[4] += 1, Neighbor::Replace(_, _, _, _) => acc[5] += 1, } } if cur_cost < best_cost { best_sol = cur_sol.clone(); best_cost = cur_cost; eprintln!("{} {}", iter, best_cost); } } eprintln!("{:?}", acc); (best_sol, best_cost) } fn solve(input: &Input, timer: &Timer, tl: u64) -> Vec { let used_bombs: Vec<(usize, Pos)> = { let tmp = list_bomb_use(&input); let order = solve_tsp(&tmp.iter().map(|(_, p)| *p).collect()); order.iter().map(|i| tmp[*i]).collect() }; let initial_sol = used_bombs.iter().map(|x| vec![*x; 1]).collect(); eprintln!("initial solution {}", timer.get_elapsed_time()); let iter_count = 4; let sol = (0..iter_count).map(|i| { let cur_time = timer.get_elapsed_time(); let rem_time = tl - cur_time; let rem_count = iter_count - i; let dur = rem_time / rem_count; split_uselist(input, &initial_sol, i as u32, timer, cur_time + dur) }) .min_by_key(|(_, cost)| *cost) .unwrap() .0; let mut ans = Vec::new(); let mut p = Pos{x: 0, y: 0}; let mut A = input.A.clone(); for use_list in &sol { let shop = { let shops: Vec = { let mut result = Vec::new(); for x in 0..N { for y in 0..N { if A[x][y] == '@' { result.push(Pos{x: x as i64, y: y as i64}) } } } result }; assert!(!shops.is_empty()); let weight = (use_list.len() + 1) * (use_list.len() + 1); let q = use_list[0].1; let nearest_shop = |p: Pos| { *shops.iter().min_by_key(|s| L1(p, **s) + weight * L1(**s, q)).unwrap() }; nearest_shop(p) }; // buy for (i, _) in use_list { ans.push(Move::Buy{bomb: *i, shop_pos: shop}); } // use for (i, q) in use_list { ans.push(Move::Use{bomb: *i, pos: *q}); p = *q; for diff in &input.offsets[*i] { let p1 = p + diff; if p1.is_valid() { let Pos{x, y} = p1; A[x as usize][y as usize] = '.'; } } } } ans } fn write_ans(ans: &Vec) { println!("{}", ans.len()); for m in ans { match m { AtomicMove::Walk(d) => println!("1 {}", d), AtomicMove::Buy(i) => println!("2 {}", i + 1), AtomicMove::Use(i) => println!("3 {}", i + 1), } } } fn calc_score(input: &Input, ans: &Vec) -> (u64, u64) { let mut cost: u64 = 0; let mut A = input.A.clone(); let mut p = (0, 0); let mut bomb_stock = [0; M]; let mut total_bombs = 0; for m in ans { use AtomicMove::{Walk, Buy, Use}; match m { Walk(m) => { match m { Dir::L => { assert!(p.1 > 0); p.1 -= 1; }, Dir::R => { assert!(p.1 + 1 < N); p.1 += 1; } Dir::D => { assert!(p.0 + 1 < N); p.0 += 1; } Dir::U => { assert!(p.0 > 0); p.0 -= 1; } } let base_cost = (total_bombs + 1) * (total_bombs + 1); let weight = if A[p.0][p.1] == '.' { 1 } else { 2 }; cost += base_cost * weight; } Buy(i) => { assert!(*i < M); cost += input.costs[*i] as u64; bomb_stock[*i] += 1; total_bombs += 1; } Use(i) => { assert!(*i < M); assert!(bomb_stock[*i] > 0); bomb_stock[*i] -= 1; total_bombs -= 1; for Diff{dx, dy} in &input.offsets[*i] { let x = p.0 as i64 + dx; let y = p.1 as i64 + dy; if 0 <= x && x < N as i64 && 0 <= y && y < N as i64 { A[x as usize][y as usize] = '.'; } } } } } if (0..N).any(|i| (0..N).any(|j| A[i][j] != '.')) { (1, cost) } else { ((1e12 / (1e4 + cost as f64)).floor() as u64, cost) } } fn convert_solution(input: &Input, sol: &Vec) -> Vec { let mut cur_pos = Pos{x: 0, y: 0}; let mut ans = Vec::new(); let mut A = input.A.clone(); for m in sol { match m { Move::Buy{bomb, shop_pos} => { assert!({ let Pos{x, y} = shop_pos; A[*x as usize][*y as usize] == '@' }); for d in shortest_path(&A, cur_pos, *shop_pos) { ans.push(AtomicMove::Walk(d)) } ans.push(AtomicMove::Buy(*bomb)); cur_pos = *shop_pos; } Move::Use{bomb, pos} => { // let mut count = 0; for d in shortest_path(&A, cur_pos, *pos) { ans.push(AtomicMove::Walk(d)) } ans.push(AtomicMove::Use(*bomb)); cur_pos = *pos; for diff in &input.offsets[*bomb] { let p1 = cur_pos + diff; if p1.is_valid() { let Pos{x, y} = p1; // if A[x as usize][y as usize] != '.' { count += 1 } A[x as usize][y as usize] = '.'; } } // eprintln!("{} {} {} {}", bomb, pos.x, pos.y, count); } } } ans } fn main() { let timer = Timer::new(); let input = read_input(); let sol = solve(&input, &timer, 2900); let ans = convert_solution(&input, &sol); write_ans(&ans); let (score, cost) = calc_score(&input, &ans); eprintln!("calculated score, cost = {} {}", score, cost); } mod rand { pub struct Rng { x: u32, y: u32, z: u32, w: u32 } impl Rng { pub fn new(seed: u32) -> Rng { Rng { x: 123456789, y: 362436069, z: 521288629, w: 88675123 ^ seed, } } pub fn next(&mut self) -> u32 { let t = self.x ^ (self.x << 11); self.x = self.y; self.y = self.z; self.z = self.w; self.w = (self.w ^ (self.w >> 19)) ^ (t ^ (t >> 8)); t } pub fn rand_bool(&mut self, prob: f64) -> bool { let x = (prob * (1u64 << 32) as f64) as u32; self.next() < x } pub fn uniform_u32(&mut self, lb: u32, ub: u32) -> u32 { assert!(lb < ub); lb + self.next() % (ub - lb) } } } struct Timer { start: std::time::SystemTime } impl Timer { fn new() -> Timer { let start = std::time::SystemTime::now(); Timer {start} } fn get_elapsed_time(&self ) -> u64 { let dur = std::time::SystemTime::now().duration_since(self.start).unwrap(); dur.as_millis() as u64 } } fn solve_tsp(pos_list: &Vec) -> Vec { let n = pos_list.len(); let calc_cost = |ans: &Vec| -> usize { let mut cost = 0; for i in 1..ans.len() { let w = n - i + 1; let prev_pos = pos_list[ans[i - 1]]; let next_pos = pos_list[ans[i]]; cost += L1(prev_pos, next_pos) * w * w; } cost }; let mut cur_ans = (0..pos_list.len()).collect(); let mut cur_cost = calc_cost(&cur_ans); let mut best_ans = cur_ans.clone(); let mut best_cost = cur_cost; let max_iter = 100_000; let mut rng = Rng::new(0); let rand_range = |n: usize, rng: &mut Rng| { loop { let i = rng.uniform_u32(0, n as u32) as usize; let j = rng.uniform_u32(0, n as u32) as usize; if i > j { break (j, i) } if i < j { break (i, j) } } }; let accept = |cur_cost: usize, new_cost: usize, progress: f64, rng: &mut Rng| -> bool { if new_cost <= cur_cost { true } else { let start_temp = 10000.0; let end_temp = 1.0; let temp = start_temp + (end_temp - start_temp) * progress; let prob = ((cur_cost as f64 - new_cost as f64) / temp).exp(); rng.rand_bool(prob) } }; for iter in 0..max_iter { let (i, j) = rand_range(pos_list.len(), &mut rng); // eprintln!("{}", cur_ans.iter().join(" ")); // eprintln!("rev {} {}", i, j); cur_ans[i..j].reverse(); // eprintln!("-> {}", cur_ans.iter().join(" ")); let tmp_cost = calc_cost(&cur_ans); if accept(cur_cost, tmp_cost, iter as f64 / max_iter as f64, &mut rng) { cur_cost = tmp_cost; } else { cur_ans[i..j].reverse(); } if cur_cost < best_cost { best_cost = cur_cost; best_ans = cur_ans.clone(); // eprintln!("{} {}", iter, best_cost); } } best_ans }