Problem:
Data Center on Fire

今から少し未来の話である．高密度記憶および長期保存可能である記録デバイスが開発されてきたが，コンテンツが生み出される速度が異常なほどに速かったため，データセンターを設立した．データセンターを設立した当初は小さな建物だったが，増え続けるデータにあわせて何度も拡張工事をしたため，性能の異なるエレベータを何基も持つこととなった．

ところが，データセンターで火災が発生した．火は一定時間だけ経過すると上の階ないし下の階に燃え広がる．また，火がついてから一定時間だけ経過するとその階は焼失してしまう．そこでエレベータを使ってデバイスを運び出すこととした．このエレベータは耐火性能が完璧であるため，階の焼失に関わらず任意の階へ移動できる．また，非常に強力な加速減速装置を持つことから，加速および減速にかかる時間は無視できるほど小さいため，一瞬にして定常速度に加速すること，あるいは停止することができる．また，緊急時においてはいわゆる「開」ボタンが機能しないようにプログラムされているため，エレベータの停止時間は運び出す（あるいは降ろす）デバイスの個数にかかわらず一定である．また，階の焼失前に到着したエレベータによって運び出されるデバイスについては，たとえエレベータの出発前に階が焼失したとしても，焼失の影響を受けない．ただし，エレベータに乗せることができなかったデバイスは当然ながら焼失する．

エレベータはどの階から回収してよいか分からないため，デバイスのある最上階を目指すようにプログラムされている．ただし，エレベータ同士は通信可能であり，別のエレベータが目的階に到着した瞬間に情報が通信される．到着したエレベータに全てのデバイスが積みこめることがわかったときは，目的階よりも下にあり，回収可能なデバイスが残っている階のうちで最上階に目的階を変更する．また，焼失のために目的階に向かう必要性が失われたときも，同様にして目的階を変更する．目的階を変更するときに，移動方向を変更する必要があるときは，即座に移動方向を変更する．また，エレベータが満杯になってそれ以上のデバイスを積みこむことができないとき，あるいは回収可能なデバイスが残っていないときは 1 階を目指す．

あなたの仕事は，上記の条件のもとで，退避させることのできたデバイスの個数および到着時刻を求めるプログラムを作成することである．

Input

入力は複数のデータセットから構成される．それぞれのデータセットは次の形式で与えられる．

N M

d

n1 n2 ... nN

c1 v1 ts1 x1

c2 v2 ts2 x2

...

cM vM tsM xM
k tx ty tz

記号の意味は次のとおりである．入力中の値はすべて整数である．

N (2 <= N <= 30)，M (1 <= M <= 10)はそれぞれビルの階数，エレベータの基数を表す．

d (1000 <= d <= 10000)は階と階の間の距離を表す．

ni (0 <= ni <= 100) は i 階にあるデバイスの個数を表す．

ci (1 <= ci <= 50)，vi (1 <= vi <= 2000)，tsi (1 <= tsi <= 20)，xi (1 <= xi <= N)はそれぞれ i 番目のエレベータの容量，速度，停止時間，初期位置をあらわす．初期位置は何階にあるかで表される．

k (2 <= k <= N)，tx (30 <= tx <= 300)，ty (30 <= ty <= 300)，tz (30 <= tz <= 300)はそれぞれ火元の階，火がついてから焼失するまでの時間，上に燃え移るまでの時間，下に燃え移るまでの時間を表す．

入力の終了は 2 つの 0 を含む行によってあらわされる．これはデータセットの一部ではない．

それぞれのデータセットは次の条件を満たすと仮定してよい．

単位時間の 1/1000 よりも短い時間の間に複数の階の消失が起こることはない．

単位時間の 1/1000 よりも短い時間の間に複数のエレベータが同一の階で 1 階より上にある階に到着することない．

単位時間の 1/1000 よりも短い時間の間にエレベータの到着とそのエレベータが到着しようとした階の焼失が起こることはない．

Output

それぞれのテストケースについて，回収されたデバイスの個数，および最後に回収されたデバイスが 1 階に到着してエレベータから降ろされるまでの時間を，単一の空白で区切って 1 行で出力しなさい．ただし，時間については，小数点以下に何個の数字を出力しても構わないが，0.001 を超える誤差を含めてはならない．また，1 階にあるデバイス以外は回収できなかったときは時間としてゼロを出力しなさい．

Sample Input

5 2
5000
10 20 0 30 5
10 1000 6 1
20 500 8 1
3 40 25 30
0 0

Output for the Sample Input

50 84.000

Solution in Kotlin (judge verdict: Accepted)

import java.lang.Math.*

open class Floor(val position:Int, val height:Int, deviceCount: Int, private val timeWhileBurnOut:Int){
    private var mFireSpreaded:Boolean = false
    var nextSchedule:FireEvent = NoEvent
        private set
    var restDevice:Int = deviceCount
        private set
    var isBurnedOut:Boolean = false
        private set
    open val hasNoDevice:Boolean
        get() = restDevice == 0
    open fun loadToElevator(deviceCount:Int):Unit {
        restDevice -= deviceCount
    }
    open fun fireSpreadAt(time:Double):Unit {
        if (!mFireSpreaded){
            mFireSpreaded = true
            nextSchedule = SpreadFire(this, time)
        }
    }
    open fun fireSpread():Unit {
        //println("Spread Fire at $this")
        nextSchedule = BurnOut(this, nextSchedule.time + timeWhileBurnOut)
    }
    open fun burnOut():Unit {
        //println("$this is BurnedOut")
        isBurnedOut = true
        nextSchedule = NoEvent
    }

    override fun toString(): String {
        return when {
            isBurnedOut -> "F(${position}, BurnedOut)"
            hasNoDevice -> "F(${position}, _NoDevice_)"
            else -> "F($position, D[$restDevice])"
        }
    }
    open val isGroundFloor:Boolean = false
}
object Ground:Floor(0, 0, 0, 0){
    override val hasNoDevice:Boolean
        get() = false
    override fun loadToElevator(deviceCount: Int) = Unit
    override fun fireSpreadAt(time: Double) = Unit
    override fun fireSpread() = Unit
    override fun burnOut() = Unit
    override fun toString(): String {
        return "GroundFloor"
    }

    override val isGroundFloor: Boolean = true
}
class Elevator(capacity:Int, velocity:Int, stopTime:Int, initPosition:Floor, val id:Int){
    private val mCapacity:Int = capacity
    private val mVelocity:Double = velocity.toDouble()
    private val mStopTime:Int = stopTime
    private var mStopPosition:Double = initPosition.height.toDouble()
    private var mLeaveTime:Double = 0e0
    var nextSchedule:ElevatorEvent = NoEvent
        private set
    val capacity:Int
        get() = mCapacity - loadedDevice
    var loadedDevice:Int = 0
        private set
    val isMovingToTop:Boolean
        get() = nextSchedule is ReachTop
    fun moveToLoad(floor:Floor):Unit {
        //println("$this move to $floor, capacity = ${this.capacity}")
        mLeaveTime = nextSchedule.time
        nextSchedule = reach(floor, this, nextSchedule.time + abs(floor.height - mStopPosition) / mVelocity)
    }
    fun changeTargetFloorAt(floor:Floor, time:Double):Unit {
        //println("Elevator[$id] change direction from [${(nextSchedule as ReachTop).floor}] to [${floor}]")
        val prevSchedule = nextSchedule as ReachTop
        mStopPosition += (time - mLeaveTime) / (prevSchedule.time - mLeaveTime) * (prevSchedule.floor.height - mStopPosition)
        mLeaveTime = time
        nextSchedule = reach(floor, this, time + abs(floor.height - mStopPosition) / mVelocity)
    }
    fun moveToUnload():Unit {
        //println("Elevator[$this] move to Ground. loaded = ${this.loadedDevice}")
        mLeaveTime = nextSchedule.time
        nextSchedule = ReachGround(this, nextSchedule.time + mStopPosition / mVelocity)
    }
    fun loadDevice(deviceCount:Int):Unit {
        //println("E$id loadDevice $deviceCount")
        mStopPosition = (nextSchedule as ReachTop).floor.height.toDouble()
        when(deviceCount){
            0 -> nextSchedule = NoEvent
            else -> {
                loadedDevice += deviceCount
                nextSchedule = LoadEnd(this, nextSchedule.time + mStopTime)
            }
        }
    }
    fun unloadDevice():Unit {
        //println("E$id unloadDevice")
        mStopPosition = 0e0
        loadedDevice = 0
        nextSchedule = UnloadEnd(this, nextSchedule.time + mStopTime)
    }
    fun elevatorShutDown():Unit {
        //println("$this is ShutDown")
        nextSchedule = NoEvent
    }
    override fun toString(): String {
        val next = nextSchedule
        return when (next){
            is ReachTop -> "E([$id] Move To ${next.floor})"
            is ReachGround -> "E([$id] To Ground, D[$loadedDevice])"
            is LoadEnd -> "E([$id] Loading)"
            is UnloadEnd -> "E([$id] Unloading)"
            else -> "E([$id] is Offline)"
        }
    }
}
fun reach(floor: Floor, elevator: Elevator, time:Double):ElevatorEvent{
    return when{
        floor.isGroundFloor -> ReachGround(elevator, time)
        else -> ReachTop(floor, elevator, time)
    }
}
interface Event{
    val time:Double
    val hasSchedule:Boolean
        get() = true
}
interface FireEvent:Event
interface ElevatorEvent:Event
interface BuildingEvent:Event
class BurnOut(val floor:Floor, override val time:Double):FireEvent, BuildingEvent{
    override fun toString(): String {
        return "BurnOut $floor, at $time"
    }
}
class SpreadFire(val floor:Floor, override val time:Double):FireEvent {
    override fun toString(): String {
        return "SpreadFire $floor, at $time"
    }
}
class ReachTop(val floor:Floor, val elevator: Elevator, override val time:Double):ElevatorEvent, BuildingEvent{
    override fun toString(): String {
        return "ReachTop $elevator at $floor, at $time"
    }
}
class LoadEnd(val elevator:Elevator, override val time:Double):ElevatorEvent, BuildingEvent {
    override fun toString(): String {
        return "LoadEnd $elevator, at $time"
    }
}
class UnloadEnd(val elevator: Elevator, override val time:Double):ElevatorEvent, BuildingEvent{
    override fun toString(): String {
        return "UnloadEnd $elevator, at $time"
    }
}
class ReachGround(val elevator:Elevator, override val time:Double):ElevatorEvent, BuildingEvent {
    override fun toString(): String {
        return "ReachGround $elevator, at $time"
    }
}
object NoEvent:FireEvent, ElevatorEvent, BuildingEvent{
    override fun toString(): String {
        return "NoEvent"
    }
    override val time:Double = 0e0
    override val hasSchedule: Boolean
        get() = false
}

class Building private constructor(private val mFloors:Array<Floor>, private val mElevators:Iterable<Elevator>, private val timeWhileSpreadUp:Int, private val timeWhileSpreadDown:Int, deviceAlreadyInFirstFloor:Int){
    override fun toString(): String {
        return "Building [$deviceInGround] " + mFloors.joinToString(" ") + "\n" + mElevators.joinToString(" ") + "\n" + topFloor
    }
    private fun mostRecentlyEvent():Event {
        return listOf<Iterable<Event>>(mFloors.map { it.nextSchedule }.filter{it.hasSchedule}, mElevators.map{it.nextSchedule}.filter{it.hasSchedule}).flatten().minBy{it.time} ?: NoEvent
    }
    var nextSchedule:BuildingEvent = NoEvent
        private set
    var topFloor:Floor = mFloors.findLast { !it.isBurnedOut && !it.hasNoDevice }!!
        private set
    val elevators:Iterable<Elevator> = mElevators
    var deviceInGround:Int = deviceAlreadyInFirstFloor
    fun eventExhaustion():Unit {
        loop@while (true){
            val event = mostRecentlyEvent()
            when(event){
                is BuildingEvent -> {
                    nextSchedule = event
                    break@loop
                }
                is SpreadFire -> {
                    event.floor.fireSpread()
                    if (mFloors.indices.contains(event.floor.position))
                        mFloors[event.floor.position].fireSpreadAt(event.time + timeWhileSpreadUp)
                    if (mFloors.indices.contains(event.floor.position - 2))
                        mFloors[event.floor.position - 2].fireSpreadAt(event.time + timeWhileSpreadDown)
                }
            }
        }
    }
    fun renewTopFloor():Unit{
        topFloor = mFloors.findLast { !it.isBurnedOut && !it.hasNoDevice }!!
    }
    companion object {
        fun read(m:Int, line:() -> String):Building {
            val d = line().toInt()
            val floorPram = line().split(' ').map(String::toInt)
            val elevatorParam = Array(m){line().split(' ').map(String::toInt)}
            val (k, tx, ty, tz) = line().split(' ').map(String::toInt)
            val floor = floorPram.mapIndexed{ i, n -> Floor(i + 1, i * d, n, tx)}.toTypedArray()
            floor[0] = Ground
            floor[k - 1].fireSpreadAt(0e0)
            val elevator = elevatorParam.mapIndexed{index, l ->
                val (c, v, t, x) = l
                Elevator(c, v, t, floor[x - 1], index + 1)
            }
            val res = Building(floor, elevator, ty, tz, floorPram.first())
            res.elevators.forEach { it.moveToLoad(res.topFloor) }
            res.eventExhaustion()
            return res
        }
    }
}
fun min(a:Int, b:Int):Int = if (a > b) b else a
fun solve(building:Building):Unit {
    var lastUnloadEndTime:Double = 0e0
    with(building){
        loop@while (true){
            val event = nextSchedule
            //println("\n" + event)
            //println(building)
            when(event){
                is BurnOut -> {
                    event.floor.burnOut()
                    if (topFloor.isBurnedOut) {
                        renewTopFloor()
                        elevators
                                .filter { it.isMovingToTop }
                                .forEach { it.changeTargetFloorAt(topFloor, event.time) }
                    }
                }
                is ReachTop -> {
                    val canLoadDevice = min(event.elevator.capacity, event.floor.restDevice)
                    event.elevator.loadDevice(canLoadDevice)
                    event.floor.loadToElevator(canLoadDevice)
                    if (event.floor.hasNoDevice) {
                        renewTopFloor()
                        elevators.filter { it.isMovingToTop }
                                .forEach { it.changeTargetFloorAt(topFloor, event.time) }
                    }
                }
                is ReachGround -> {
                    if (event.elevator.loadedDevice == 0){
                        event.elevator.elevatorShutDown()
                    }else {
                        deviceInGround += event.elevator.loadedDevice
                        event.elevator.unloadDevice()
                    }
                }
                is LoadEnd -> {
                    if (event.elevator.capacity != 0) event.elevator.moveToLoad(topFloor)
                    else event.elevator.moveToUnload()
                }
                is UnloadEnd -> {
                    if (topFloor.isGroundFloor) {
                        event.elevator.elevatorShutDown()
                    } else {
                        event.elevator.moveToLoad(topFloor)
                    }
                    lastUnloadEndTime = event.time
                }
                is NoEvent -> break@loop
            }
            eventExhaustion()
        }
    }
    println("${building.deviceInGround} $lastUnloadEndTime")
}
fun main(args:Array<String>):Unit {
    var (n, m) = readLine()!!.split(' ').map(String::toInt)
    while (n != 0 || m != 0) {
        solve(Building.read(m) { readLine()!! })
        val (nn, mm) = readLine()!!.split(' ').map(String::toInt)
        n = nn
        m = mm
    }
}