|
// the one-click solution equals to |
|
// - the click to the zero cell, contained in the only 8-neighbor-connected zeroes |
|
// or |
|
// - the click to the non-zero cell which is the only non-mine |
|
// |
|
// observation: |
|
// with an exception of the latter case, the former case is equivalent to |
|
// *erasing* mines with the consecutive (8-neighbor-connected) area of clicks |
|
// |
|
// ***...* <- this is not a solution (zeroes are connected, clicks are not) |
|
// *...c.* since clicking one `c` will reavel its neighbors but not others |
|
// *.c...* |
|
// *...*** |
|
// |
|
// further observation: |
|
// once you have a solution, you can transform the solution into the following form: |
|
// |
|
// c.............* |
|
// ..............* |
|
// .........****** |
|
// ......********* |
|
// ......********* |
|
// *************** |
|
// |
|
// i.e. you are finding some rectangle larger than 2x2, followed by another rectangle |
|
// which height is smaller than the prior one, and vice versa. |
|
// (the height of the final rectangle should be at least 2.) |
|
// |
|
// this gives a simple recurrence equation: |
|
// C(n, m, k) returns true when n by m table with k *non-mines* is possible to solve by one click, |
|
// assuming that the first one column is known to be non-mines and that column is duplicated right on that. |
|
// |
|
// - C(1, m, k) = (m >= 2) and (m = k) |
|
// c c c |
|
// . . . |
|
// . . |
|
// . |
|
// |
|
// - for n>=2, C(n, m, k) = (k >= m) and (m = k or C(n-1, 2, k-m) or C(n-1, 3, k-m) or ... or C(n-1, m, k-m)) |
|
// c## c## c## c** |
|
// .## .## .## .** note that ## covers one column which is already known to be non-mines, |
|
// .## .## .** .** which is just fine since this assumption is included to the definition of C. |
|
// .## .** .** .** |
|
// |
|
// so that the final solution would be: |
|
// possible(n, m, 0) = false ******** |
|
// possible(n, m, 1) = true c******* |
|
// possible(1, m, k) = true iff 0 <= k <= m c.**** c..*** c...** c....* |
|
// possible(n, 1, k) = true iff 0 <= k <= n |
|
// possible(n, m, k) for n>=2, m>=2, 1<k<n*m |
|
// = C(n-1, m, k-m) or C(n-1, m-1, k-(m-1)) or ... or C(n-1, 2, k-2) |
|
// c##### c##### c##### |
|
// .##### .##### .##### so that we *require* the first two columns are non-mines, |
|
// .##### .##### ****** as we first expected. |
|
// .##### ****** ****** |
|
|
|
extern crate collections; |
|
|
|
use std::io; |
|
use std::str; |
|
use collections::HashMap; |
|
|
|
#[deriving(Eq, TotalEq, Hash, Clone, Show)] |
|
struct Subprob(uint, uint, uint); // n, m, k |
|
|
|
#[deriving(Eq, Clone, Show)] |
|
enum Trace { |
|
FillMine, |
|
FillThenAdvance(uint, uint, uint), // left, top, new_m |
|
} |
|
|
|
type Memo = HashMap<Subprob, Option<Trace>>; |
|
|
|
fn solve_subproblem(memo: &mut Memo, Subprob(n, m, k): Subprob) -> Option<Trace> { |
|
//println!("solve_subproblem({}, {}, {}) = ...", n, m, k); |
|
let ret = if n <= 0 { |
|
None |
|
} else if n == 1 { |
|
if m >= 2 && k == n * m { |
|
Some(FillThenAdvance(n, m, 0)) |
|
} else { |
|
None |
|
} |
|
} else { // n >= 2 |
|
if k < m { return None; } |
|
|
|
// try the memo first |
|
match memo.find_copy(&Subprob(n, m, k)) { |
|
Some(maybe_trace) => return maybe_trace, |
|
None => {} |
|
} |
|
|
|
let mut ret = None; |
|
if m == k { |
|
ret = Some(FillThenAdvance(1, m, 0)); |
|
} |
|
for i in range(2, m+1).rev() { |
|
let subret = solve_subproblem(memo, Subprob(n-1, i, k-m)); |
|
if subret.is_some() { |
|
ret = Some(FillThenAdvance(1, m, i)); |
|
break; |
|
} |
|
} |
|
memo.insert(Subprob(n, m, k), ret); |
|
ret |
|
}; |
|
//println!("solve_subproblem({}, {}, {}) = {}", n, m, k, ret); |
|
ret |
|
} |
|
|
|
fn solve_problem(memo: &mut Memo, Subprob(n, m, k): Subprob) -> Option<Trace> { |
|
//println!("solve_problem({}, {}, {}) = ...", n, m, k); |
|
if k == 0 || k > n * m { return None; } |
|
if k == 1 { return Some(FillThenAdvance(1, 1, 0)); } |
|
if k == n * m { return Some(FillThenAdvance(n, m, 0)); } |
|
if n == 1 { return Some(FillThenAdvance(1, k, 0)); } |
|
if m == 1 { return Some(FillThenAdvance(k, 1, 0)); } |
|
|
|
for i in range(2, m+1).rev() { |
|
if k < i { continue; } |
|
let subret = solve_subproblem(memo, Subprob(n-1, i, k-i)); |
|
if subret.is_some() { |
|
return Some(FillThenAdvance(1, i, i)); |
|
} |
|
} |
|
None |
|
} |
|
|
|
fn solve(memo: &mut Memo, Subprob(n, m, k): Subprob) -> Option<Vec<Vec<char>>> { |
|
let ret = solve_problem(memo, Subprob(n, m, k)); |
|
if ret.is_none() { return None; } |
|
let mut ret = ret.unwrap(); |
|
|
|
let mut tab = Vec::from_fn(m, |_| Vec::from_elem(n, '*')); |
|
let mut x = 0; |
|
let mut k = k; |
|
loop { |
|
//println!("tracing: n={} m={} k={} x={} ret={}", n, m, k, x, ret); |
|
match ret { |
|
FillMine => break, |
|
FillThenAdvance(w, h, m) => { |
|
for i in range(0, h) { |
|
for j in range(x, x+w) { |
|
tab.as_mut_slice()[i].as_mut_slice()[j] = '.'; |
|
} |
|
} |
|
x += w; |
|
k -= w * h; |
|
if m == 0 { break; } |
|
ret = solve_subproblem(memo, Subprob(n - x, m, k)).unwrap(); |
|
} |
|
} |
|
} |
|
assert!(k == 0); |
|
|
|
assert!(tab.as_slice()[0].as_slice()[0] == '.') |
|
tab.as_mut_slice()[0].as_mut_slice()[0] = 'c'; |
|
|
|
Some(tab) |
|
} |
|
|
|
fn read_line(r: &mut Buffer) -> ~str { |
|
r.read_line().unwrap().trim().to_owned() |
|
} |
|
|
|
fn case(memo: &mut Memo, r: &mut Buffer) -> Option<Vec<Vec<char>>> { |
|
let nums: Vec<_> = read_line(r).words().map(|s| from_str::<uint>(s).unwrap()).collect(); |
|
assert_eq!(nums.len(), 3); |
|
let nrows = nums.as_slice()[0]; |
|
let ncols = nums.as_slice()[1]; |
|
let nmines = nums.as_slice()[2]; |
|
// we use somewhat different definitions for easier understanding |
|
solve(memo, Subprob(/*n*/ncols, /*m*/nrows, /*k*/ncols * nrows - nmines)) |
|
} |
|
|
|
#[cfg(test)] |
|
fn case_str(memo: &mut Memo, r: &str) -> Option<~str> { |
|
let mut r_ = io::BufReader::new(r.as_bytes()); |
|
match case(memo, &mut r_) { |
|
Some(v) => { |
|
let mut s = ~""; |
|
for (i, line) in v.move_iter().enumerate() { |
|
if i > 0 { s.push_char('\n'); } |
|
s.push_str(str::from_chars(line.as_slice())); |
|
} |
|
Some(s) |
|
}, |
|
None => None |
|
} |
|
} |
|
|
|
#[test] |
|
fn test_sample() { |
|
let mut memo = HashMap::new(); |
|
assert_eq!(case_str(&mut memo, "5 5 23"), None); |
|
assert_eq!(case_str(&mut memo, "3 1 1"), Some(~"c\n\ |
|
.\n\ |
|
*")); |
|
assert_eq!(case_str(&mut memo, "2 2 1"), None); |
|
assert_eq!(case_str(&mut memo, "4 7 3"), Some(~"c......\n\ |
|
.......\n\ |
|
......*\n\ |
|
.....**")); |
|
assert_eq!(case_str(&mut memo, "10 10 82"), Some(~"c.********\n\ |
|
..********\n\ |
|
..********\n\ |
|
..********\n\ |
|
..********\n\ |
|
..********\n\ |
|
..********\n\ |
|
..********\n\ |
|
..********\n\ |
|
**********")); |
|
} |
|
|
|
#[test] |
|
fn test_small() { |
|
let mut memo = HashMap::new(); |
|
assert_eq!(case_str(&mut memo, "5 5 0"), Some(~"c....\n\ |
|
.....\n\ |
|
.....\n\ |
|
.....\n\ |
|
.....")); |
|
assert_eq!(case_str(&mut memo, "5 5 1"), Some(~"c....\n\ |
|
.....\n\ |
|
.....\n\ |
|
.....\n\ |
|
....*")); |
|
assert_eq!(case_str(&mut memo, "5 5 2"), Some(~"c....\n\ |
|
.....\n\ |
|
.....\n\ |
|
....*\n\ |
|
....*")); |
|
assert_eq!(case_str(&mut memo, "5 5 3"), Some(~"c....\n\ |
|
.....\n\ |
|
....*\n\ |
|
....*\n\ |
|
....*")); |
|
assert_eq!(case_str(&mut memo, "5 5 4"), Some(~"c....\n\ |
|
.....\n\ |
|
....*\n\ |
|
....*\n\ |
|
...**")); |
|
assert_eq!(case_str(&mut memo, "5 5 5"), Some(~"c...*\n\ |
|
....*\n\ |
|
....*\n\ |
|
....*\n\ |
|
....*")); |
|
assert_eq!(case_str(&mut memo, "5 5 9"), Some(~"c...*\n\ |
|
....*\n\ |
|
...**\n\ |
|
...**\n\ |
|
..***")); |
|
assert_eq!(case_str(&mut memo, "5 5 16"), Some(~"c..**\n\ |
|
...**\n\ |
|
...**\n\ |
|
*****\n\ |
|
*****")); |
|
assert_eq!(case_str(&mut memo, "5 5 17"), Some(~"c.***\n\ |
|
..***\n\ |
|
..***\n\ |
|
..***\n\ |
|
*****")); |
|
assert_eq!(case_str(&mut memo, "5 5 18"), None); |
|
assert_eq!(case_str(&mut memo, "5 5 19"), Some(~"c.***\n\ |
|
..***\n\ |
|
..***\n\ |
|
*****\n\ |
|
*****")); |
|
assert_eq!(case_str(&mut memo, "5 5 20"), None); |
|
assert_eq!(case_str(&mut memo, "5 5 21"), Some(~"c.***\n\ |
|
..***\n\ |
|
*****\n\ |
|
*****\n\ |
|
*****")); |
|
assert_eq!(case_str(&mut memo, "5 5 22"), None); |
|
assert_eq!(case_str(&mut memo, "5 5 24"), Some(~"c****\n\ |
|
*****\n\ |
|
*****\n\ |
|
*****\n\ |
|
*****")); |
|
assert_eq!(case_str(&mut memo, "5 5 25"), None); |
|
} |
|
|
|
#[test] |
|
fn test_larger() { |
|
let mut memo = HashMap::new(); |
|
for i in range(0, 2501) { |
|
case_str(&mut memo, format!("50 50 {}", i)); |
|
} |
|
} |
|
|
|
pub fn main() { |
|
let mut r = io::BufferedReader::new(io::stdin()); |
|
let t: uint = from_str(read_line(&mut r)).unwrap(); |
|
let mut memo = HashMap::new(); |
|
for i in range(1, t + 1) { |
|
println!(r"Case \#{}:", i); |
|
match case(&mut memo, &mut r) { |
|
Some(v) => { |
|
for line in v.move_iter() { |
|
println!("{}", str::from_chars(line.as_slice())); |
|
} |
|
} |
|
None => { |
|
println!("Impossible"); |
|
} |
|
} |
|
} |
|
} |