Problem:
Problem C: コンパイル

東京大学に入学したきたまさ君はTSG(東大スーパーゲーマーズ)というサークルに入った。

このサークルは毎年、駒場祭でゲームを展示しており、きたまさ君もゲームを作成して展示することにした。

きたまさ君の作成するゲームは次のような、よくある落ち物パズルゲームである。

横6マス×縦12マスの格子状のフィールドがあり、各マスにつき1個のブロックを置くことができる。

上からブロックが2つ1組で落下してくる。ブロックには赤・緑・青・黄・紫の5色の基本ブロックとお邪魔ブロックの計6種類がある。プレイヤーはブロックを回転、横移動により操作し、落下位置を変更することができる。

落下してきたブロックがフィールドの床やほかのブロックに衝突すると、その位置にブロックが固定される。

固定されたブロックと同色のブロックが周囲4方向にある場合、それらは互いにくっつく。ただし、お邪魔ブロックは互いにくっつかない。

ブロックが4個以上くっつくと消滅し得点となる。お邪魔ブロックの周囲4方向に存在する基本ブロックが消滅すると、そのお邪魔ブロックも一緒に消滅する。ブロックの消滅は、全てのブロックの落下が終了したタイミングで同時に起こる。

ブロックの消滅により、その上にあったブロックが落下する。このブロックの落下はプレイヤーが操作できない。このとき再びブロックが4個以上くっつくと消滅し、連鎖が起きる。ただし、複数色を同時に消した場合でも、1連鎖扱いとなる。

きたまさ君はこのゲームの大部分のプログラムを書き終えたが、どうしてもブロックがくっついて消滅したときの処理を書くことが出来なかった。そこでサークルの友人であるあなたにその部分のプログラムを書いてもらうことにした。

Input

入力は12行からなる。

入力の i 行目は長さ6の文字列からなり、その j 文字目はフィールドの上から i 行目、左から j 列目のマスの状態を表している。

マスの状態を表す文字は以下の7つのうちのいずれかである。

文字

種類

R

赤

G

緑

B

青

Y

黄

P

紫

O

お邪魔

.

空きマス

また、入力に含まれる全てのブロックは床の上か他のブロックの上に乗っている。

Output

入力の状態から、ルールに従ってブロックを消していった時の連鎖数を計算し、一行で出力せよ。

ブロックが一つも消えない場合は0と出力せよ。

Notes on Test Cases

上記入力形式で複数のデータセットが与えられます。

入力の1行目にデータセットの数Tが与えられます。

各データセットに対して上記出力形式で出力を行うプログラムを作成して下さい。

Sample Input

3
......
......
......
......
......
......
......
......
.RGB..
RGOP..
RGBPB.
RGBPP.
GBRGYP
GBRRYP
BGGRBB
BPRGYY
GGPRRY
BYPPRB
YGGGPB
GYYYPR
YRBRBR
YBRBRB
BRBRBR
BRBRBR
......
......
......
......
......
......
......
......
......
......
..OO..
..OO..

Output for Sample Input

3
18
0

Solution in Kotlin (judge verdict: Accepted)


fun main(args:Array<String>):Unit {
    repeat(readLine()!!.toInt()){
        println(countRensa(Array(12){readLine()!!.trim()}))
    }
}
class UnionFindTree{
    private var mRank:Int = 1
    private var mParent:UnionFindTree? = null
    private fun parent():UnionFindTree {
        return when(mParent){
            null -> this
            else -> {
                mParent = mParent?.parent()
                mParent!!
            }
        }
    }
    fun rank():Int {
        return when(mParent){
            null -> mRank
            else -> parent().rank()
        }
    }
    fun isSameUnion(other:UnionFindTree):Boolean {
        return parent() == other.parent()
    }
    fun merge(other:UnionFindTree):Unit {
        if (!isSameUnion(other)){
            val newRank = rank() + other.rank()
            parent().mParent = other.parent()
            parent().mRank = newRank
        }
    }
}
data class Point(val x:Int, val y:Int) {
    fun neighbors():Iterable<Point> = listOf(Point(x - 1, y), Point(x + 1, y), Point(x, y - 1), Point(x, y + 1))
}
data class Puyo(val point:Point, val color:Char){
    fun down():Puyo = Puyo(Point(point.x, point.y + 1), color)
}
fun countRensa(state:Array<String>):Int {
    //println(state.joinToString("\n", postfix = "\n"))
    val puyo = state.map{it.map{UnionFindTree()}}
    val moved = state.map(String::toCharArray).toTypedArray()
    for (i in 1 until state.size) for (j in 0 until state[0].length) {
        if (state[i][j] !in listOf('O', '.') && state[i - 1][j] == state[i][j]){
            puyo[i][j].merge(puyo[i - 1][j])
        }
    }
    for (i in 0 until state.size) for (j in 1 until state[0].length){
        if (state[i][j] !in listOf('O', '.') && state[i][j - 1] == state[i][j]){
            puyo[i][j].merge(puyo[i][j - 1])
        }
    }
    for (i in 0 until state.size) for (j in 0 until state[0].length){
        if (puyo[i][j].rank() >= 4){
            moved[i][j] = '.'
            Point(j, i).neighbors().filter{it.y in 0 until state.size && it.x in 0 until state[0].length}.filter { state[it.y][it.x] == 'O' }.forEach {
                moved[it.y][it.x] = '.'
            }
        }
    }
    var movable = (0 until state.size).flatMap{y -> (0 until state[0].length).filter{x -> moved[y][x] != '.'}.map{x -> Puyo(Point(x, y), moved[y][x])}}
    while(movable.count{
                it.point.y + 1 < state.size && moved[it.point.y + 1][it.point.x] == '.'
            } != 0)  {
        val m = movable.filter{it.point.y + 1 < state.size && moved[it.point.y + 1][it.point.x] == '.'}
        movable = movable.map{
            if (it.point.y + 1 < state.size && moved[it.point.y + 1][it.point.x] == '.') {
                Puyo(it.point.copy(y = it.point.y + 1), it.color)
            }else{
                it
            }
        }
        m.forEach {
            moved[it.point.y][it.point.x] = '.'
            moved[it.point.y + 1][it.point.x] = it.color
        }
    }
    return if (puyo.any{it.any{it.rank() >= 4}}) 1 + countRensa(moved.map{it.joinToString("")}.toTypedArray())
        else 0
}