Problem:
砂の城 (Sandcastle)

問題

JOI 君は家族とともに海水浴に来た．さんざん泳いで泳ぎ疲れた JOI 君は，次は砂浜で砂の城を作って遊んだ．しばらくして砂遊びにも飽きると，JOI 君は城を放置してまた海に泳ぎに向かった．

JOI 君が作った城は，砂浜のうちのある長方形の領域に含まれている．この長方形の領域は，東西南北にマス目状に区切られている．各マスは，JOI 君が作った城の一部であるマス (以下，「城マス」と呼ぶ) かそうでないマス (以下，「更地マス」と呼ぶ) のいずれかである．それぞれの城マスには「強度」と呼ばれる 1 以上 9 以下の整数が定まっている．長方形の領域の外縁部，すなわち外側と接しているマスには城マスは存在しない．

やがて潮が満ちてきて，長方形の領域にも波が押し寄せてくるようになった．波が 1 つ押し寄せてくるたびに，城マスのうち次の条件を満たすものはすべて一斉に崩壊して，更地マスに変化する．

条件： 周囲の 8 マス (東西南北および北東，北西，南東，南西方向に隣接するマス) にある更地マスの数が，そのマスの強度の値以上である．

波が引くと，まもなく次の波が押し寄せて来る．

十分多くの波が押し寄せると，城がすべて崩れてしまうか，丈夫な部分だけが残るかして，波が押し寄せてきても 1 つも城マスが崩壊しないような状態に落ち着く．1 つ以上の城マスを崩壊させるような波が押し寄せてくる回数を求めよ．

入力

入力は 1 + H 行からなる．

1 行目には，2 つの整数 H, W (2 ≤ H ≤ 1000, 2 ≤ W ≤ 1000) が空白を区切りとして書かれている．これは，長方形の領域が縦 H 行，横 W 列の H × W 個のマスに区切られていることを表す．長方形の縦方向は南北方向であり，横方向は東西方向である．

続く H 行にはそれぞれ W 文字からなる文字列が書かれており，最初の波が押し寄せてくる前の長方形の領域の状態を表す．H 行のうちの i 行目の左から j 番目の文字 (1 ≤ i ≤ H, 1 ≤ j ≤ W) は，長方形の領域の北から i 行目，西から j 列目のマスが更地マスである場合は '.' (ピリオド) である．城マスである場合は '1', '2', ..., '9' のいずれかであり，そのマスの強度を表す．

長方形の領域の外縁部はすべて更地マスである．すなわち，すべての入力において，1 行目または H 行目の文字と，各行の左から 1 番目または W 番目の文字はすべて '.' である．

与えられる入力データのうち，入力 1 では H ≤ 50, W ≤ 50 を満たす．

出力

1 つ以上の城マスを崩壊させるような波が押し寄せてくる回数を 1 行で出力せよ．

入出力例

入力例 1

5 6
......
.939..
.3428.
.9393.
......

出力例 1

3

入力例 2

10 10
..........
.99999999.
.9.323239.
.91444449.
.91444449.
.91444449.
.91444449.
.91232329.
.99999999.
..........

出力例 2

35

入出力例 1 においては，初めに押し寄せる波で強度 3 の城マスがすべて崩壊し，

......
.9.9..
..428.
.9.9..
......

という状態になる．

次に押し寄せてくる波では強度 2 の城マスが崩壊し，

......
.9.9..
..4.8.
.9.9..
......

という状態になる．

次に押し寄せてくる波では強度 4 の城マスが崩壊し，

......
.9.9..
....8.
.9.9..
......

という状態になる．

これ以降の波が城マスを崩すことはない．よって答えは 3 である．

問題文と自動審判に使われるデータは、情報オリンピック日本委員会が作成し公開している問題文と採点用テストデータです。

Solution in Scala (judge verdict: Accepted)

import scala.annotation.tailrec
import net.pushl.io.MyConsole

// Document: http://www.scala-lang.org/api/current/#package
// -----------------------------------------------------------------------------
class Solver(val stdio: MyConsole){
  import stdio._ // shadow Console.~
                 // val diff = List((-1,0),(1,0),(0,-1),(0,1))
  val diff = (for(i <- -1 to 1;
                  j <- -1 to 1;
                  if i != 0 || j != 0) yield (i,j)).toList

  @tailrec
  final def go(fil: Array[Array[Option[Int]]], will_remove: List[(Int,Int)], ans: Int = 0) : Int =
    if(will_remove.length == 0)
      ans
    else
      go(fil, goaux(fil, will_remove), 1 + ans)

  def goaux(fil: Array[Array[Option[Int]]], will_remove: List[(Int,Int)]) : List[(Int,Int)] = {
    val he = fil.length
    val we = fil(0).length
    will_remove match {
      case Nil    => Nil
      case h :: t => h match {
        case (r,c) => {
          val inner = diff.map({case (dy,dx) => (r+dy,c+dx)}).filter({case (y,x) => 0 <= y && y < he && 0 <= x && x < we})
          val out   = inner.map({case (i,j) => {
                                   fil(i)(j) match {
                                     case None    => None
                                     case Some(k) => {
                                       if(k == 1){
                                         fil(i)(j) = None
                                         Some((i,j))
                                       }else{
                                         fil(i)(j) = Some(k-1)
                                         None
                                       }
                                     }
                                   }
                                 }}).collect({case Some(k) => k})
          out ++ goaux(fil,t)
        }
      }
    }
  }
  def main() : Unit = {
    val h,w = nextInt
    val fil = (1 to h).map(_ => readLine.map(c =>
                             if(c == '.') None
                             else         Some(c - '0')))

    val copy = (fil map (_.toArray)).toArray
    val rem  = (fil.zipWithIndex).flatMap({case (row,i) =>
                                            row.zipWithIndex.collect({case (content,j) if content == None =>
                                                                       (i,j)})}).toList
    println(go(copy,rem)-1)
  }
}









// ----------------------------------------------------------------------------0
// ----------------------------------------------------------------------------0
object Main {
  def main(args: Array[String]) : Unit = {
    val console = new MyConsole(Console.in, Console.out, Console.err)
    val solver  = new Solver(console)
    solver.main()
    console.flush()
  }
}
package net.pushl.io {
  import java.io.{BufferedReader, PrintWriter, PrintStream, PushbackReader}
  class MyConsole(val in: BufferedReader, val _out: PrintStream,
                  val err: PrintStream) {
    // PrintWriter do not flush automatically
    val out = new PrintWriter(_out,false)
    // If argument is null, there are ambiguous which function will be called
    def print(obj: Any)                  = out.print(if(obj == null) "null" else obj.toString)
    def println()                        = out.println()
    def println(obj: Any)                = out.println(obj)
    def printf(text: String, args: Any*) = out.printf(text.format(args : _*))
    // NOTE: YOU MUST FLUSH BEFORE END OF MAIN
    def flush()                          = out.flush()
    def debugln(obj: Any)                = err.println(obj)

    def readIntVector()    : Vector[Int]    = parseLineToVector(() => nextInt)
    def readLongVector()   : Vector[Long]   = parseLineToVector(() => nextLong)
    def readStringVector() : Vector[String] = parseLineToVector(() => nextString)
    def nextInt()          : Int            = nextLong().toInt
    def nextBigInt()       : BigInt         = BigInt(nextString())
    def nextBigDecimal(  ) : BigDecimal     = BigDecimal(nextString())
    def nextDouble()       : Double         = nextString().toDouble
    def nextLong() : Long = {
      if(!goNextValuable())
        throw new NoSuchElementException("Reading long failed")
      val sgn = if(peek == '-') -1l else 1
      if(sgn == -1l) read()
      if(peek < '0' || '9' < peek)
        throw new NumberFormatException(s"readLong found only '-' or no number")
      @tailrec
      def readLong(next: Int, cur: Long) : Long =
        if('0' <= next && next <= '9')
          readLong(readWithoutCheckingPeeked(), cur*10 + next-'0')
        else if(isEnd(next) || isSpaceOrControl(next))
          sgn*cur
        else
          throw new NumberFormatException(s"readLong found strange byte $next")
      val res = readLong(read(),0)
      skipTrailingSpaces()
      res
    }
    def nextString() : String = {
      if(!goNextValuable())
        throw new NoSuchElementException("Reading String failed")
      val builder = new StringBuilder
      @tailrec
      def appendCode(next: Int) : String = {
        if(isEnd(next) || isSpaceOrControl(next)){
          builder.toString
        }else{
          builder.append(next.toChar)
          appendCode(readWithoutCheckingPeeked())
        }
      }
      val res = appendCode(read())
      skipTrailingSpaces()
      res
    }
    // TODO: refactoring to marge nextString
    def readLine() : String = {
      if(isEnd(peek))
        throw new NoSuchElementException("Reading Line failed")
      val builder = new StringBuilder
      @tailrec
      def appendCode(next: Int) : String = {
        if(isEnd(next) || isNewLine(next)){
          builder.toString
        }else{
          builder.append(next.toChar)
          appendCode(read())
        }
      }
      appendCode(read())
    }
    // helpers
    private[this] var peeked: Option[Int] = None
    private[this] var last = -1
    private def read() = {
      val res = peeked match {
          case None    => in.read()
          case Some(a) => { peeked = None; a }
        }
      last = res
      res
    }
    @inline private def readWithoutCheckingPeeked() = {
      val res = in.read()
      last = res
      res
    }
    @inline private def peek() =
      peeked match {
        case None    => {
          val res = in.read()
          peeked  = Some(res)
          res
        }
        case Some(a) => a
      }
    @inline private def isEnd(c: Int)    = c == -1
    @inline private def isNewLine(c: Int) = c == 10 || c == 13     // LF and CR
    @inline private def isThereReadable() = !isEnd(peek)
    // XXX: this limits c is ASCII?
    @inline private def isSpaceOrControl(c: Int) = (0 <= c && c <= 32) || c == 127
    @tailrec
    final private def goNextNotSpaceNorControl() : Unit =
      if(isSpaceOrControl(peek)){
        read()
        goNextNotSpaceNorControl()
      }
    final private def skipTrailingSpaces() : Unit = {
      @tailrec
      def skipTrailingSpacesAux() : Unit = {
        if(!isNewLine(last) && !isNewLine(peek) && isSpaceOrControl(peek)){
          read()
          skipTrailingSpacesAux()
        }
      }
      skipTrailingSpacesAux
      if(!isNewLine(last) && isNewLine(peek)) read()
    }
    @tailrec
    final private def skipTrailingSpacesAndNewline() : Unit =
      if(isNewLine(peek)){
        val _ = read() // windows causes error. maybe.
      }else if(isSpaceOrControl(peek)){
        read()
        skipTrailingSpacesAndNewline()
      }
    @inline private def goNextValuable() = {
      goNextNotSpaceNorControl()
      isThereReadable()
    }
    @inline private def parseLineToVector[X](parser: () => X) : Vector[X] = {
      import scala.collection.immutable.VectorBuilder
      val vb = new VectorBuilder[X]()
      @tailrec
      def parseLineToVectorAux(first: Boolean=false) : Vector[X] =
        if((!first && isNewLine(last)) || isNewLine(peek) || isEnd(peek)) {
          vb.result
        }else{
          vb += parser()
          parseLineToVectorAux()
        }
      parseLineToVectorAux(true)
    }
  }
}