「電脳少女プログラミング2088 | 壊レタ君を再構築」をKotlinでやってみた

「電脳少女プログラミング2088 | 壊レタ君を再構築」をKotlinでやってみた ~~（途中）~~ ので、公開したいと思います。

https://paiza.jp/entry/denno2088_qiita

~~途中ですが、進んだら更新していきたいと思います。~~
また、解説は後で書くかもしれませんが、ランクD相当は書かないと思います。
# 郊外のスラム街（paizaランク：D相当）
```kotlin
fun main() {
    val n = readLine()!!.toInt()
    println(n / 2 + 100)
}
```
# カジノ（paizaランク：D相当）
```kotlin
fun main() {
    val dollar01 = readLine()!!.toInt()
    val dollar05 = readLine()!!.toInt()
    val dollar10 = readLine()!!.toInt()
    println(dollar01 + 5 * dollar05 + 10 * dollar10)
}
```
# ネオン街の裏路地（paizaランク：D相当）
```kotlin
private fun readInt(): Int = readLine()!!.toInt()

fun main() {
    val n = readInt()
    val max = (0 until n).map { readInt() }.maxBy { it }
    println(max)
}
```
# 自然の残る公園（paizaランク：C相当）
```kotlin
private fun line(): String = readLine()!!

fun main() {
    val (n,cx, cy) = line().split(' ').map { it.toInt() }
    val nearNo = (1..n)
        .map {
            val (x, y) = line().split(" ").map { it.toInt() }
            val l = (x-cx)*(x-cx) + (y-cy)*(y-cy)
            it to l
        }
        .minBy { (_, l) -> l }
        .first
    println(nearNo)
}
```
# ネオン街のクラブ（paizaランク：C相当）
kotlinはランキングに関係していないようですが、[除外](https://qiita.com/official-events/efe957f155af900604b3#%E3%82%AD%E3%83%A3%E3%83%B3%E3%83%9A%E3%83%BC%E3%83%B3%E5%AF%BE%E8%B1%A1%E5%95%8F%E9%A1%8C "▼キャンペーン対象問題")します。
# 廃マンションの一室（paizaランク：C相当）
```kotlin
import kotlin.math.abs

fun main() {
    val n = readLine()!!.toInt()
    var w = n
    var i = 0
    // 10進で10桁MAXとするなら3進で21桁ぐらい？（log 10^10 / log 3 ≒ = 21？）
    val t = IntArray(size = 21)
    do {
        val d = w % 3
        w /= 3
        when (val v = d + t[i]) {
            // 3は桁を上げる
            3 -> {
                t[i] = 0 // 元の桁は0
                t[i + 1] = 1 // +1として桁を上げる
            }
            // 2は桁を上げて-1したこととする
            2 -> {
                t[i] = -1 // -1する
                t[i + 1] = 1 // +1として桁を上げる
            }
            // そのまま
            1, 0, -1 -> t[i] = v
            // -2は桁を上げて+1したこととする
            -2 -> {
                t[i] = 1 // +1する
                t[i + 1] = -1 // -1として桁を上げる
            }
            // -3は桁を上げる
            -3 -> {
                t[i] = 0 // 元の桁は0
                t[i + 1] = -1 // -1として桁を上げる
            }
            // エラー
            else -> error("n=$n, i=$i, w=$w, d=$d, t[i]=${t[i]}, v=$v")
        }
        i++
    } while (abs(w) > 0 && i in t.indices)
    // 表示を修正して特殊３進数の文字列作成
    val text = t.reversed().dropWhile { it == 0 }.joinToString(separator = "") {
        when (it) {
            -1 -> "2"
            0 -> "0"
            1 -> "1"
            else -> error("Illegal value=$it")
        }
    }.ifEmpty { "0" } // 少し手抜き
    println(text)
}
```
この辺からローカルで試しながらでないと難しかった。
# ギャングのアジト（paizaランク：B相当）
```kotlin
private fun line(): String = readLine()!!

fun main() {
    val n = line().toInt()
    for(i in 1..n) {
        val l = line()
        val r = l.reversed()
        if(l != r) {
            println("No")
            return
        }
    }
    println("Yes")
}
```
手抜きです…。
```kotlin
private fun line(): String = readLine()!!

fun main() {
    val n = line().toInt()
    for(i in 1..n) {
        val l = line().split(" ").map { it.toInt() }
        val nor = l.take(n / 2)
        val rev = l.reversed().take(n / 2)
        if(nor != rev) {
            println("No")
            return
        }
    }
    println("Yes")
}
```
一応、あまり手抜きではないものです。
# 一番通りの繁華街（paizaランク：B相当）
```kotlin
private const val TERRITORY = '.'

private class Grid(private val width: Int, private val grid: List<Char>) {
    init {
        require(grid.size == width * width)
    }

    operator fun get(point: Point): Char? = get(point.x, point.y)
    operator fun get(x: Int, y: Int): Char? =
        if (x in 0 until width &&
            y in 0 until width
        ) {
            grid[y * width + x]
        } else {
            null
        }
}

private data class Point(val x: Int, val y: Int)

private fun MutableSet<Set<Point>>.addIfTerritory(base: Point, diagonal: Point, g: Grid) {
    val point2 = Point(diagonal.x, base.y)
    val point3 = Point(base.x, diagonal.y)
    if (g[base] == TERRITORY &&
        g[point2] == TERRITORY &&
        g[diagonal] == TERRITORY &&
        g[point3] == TERRITORY
    ) {
        add(setOf(base, point2, diagonal, point3))
    }
}

private fun territoriesCount(width: Int, grid: List<Char>): Int {
    val g = Grid(width, grid)
    val territories = mutableSetOf<Set<Point>>()
    for (x in 0 until width) {
        for (y in 0 until width) {
            val point = Point(x, y)
            if (g[point] != TERRITORY) continue
            for (dw in 1 until width) {
                territories.addIfTerritory(point, Point(x + dw, y + dw), g)
                territories.addIfTerritory(point, Point(x - dw, y - dw), g)
                territories.addIfTerritory(point, Point(x - dw, y + dw), g)
                territories.addIfTerritory(point, Point(x + dw, y - dw), g)
            }
        }
    }
    return territories.size
}

private fun line(): String = readLine()!!

fun main() {
    val n = line().toInt()
    val grid = (0 until n).map { line().toCharArray() }.flatMap { it.toList() }
    println(territoriesCount(n, grid).toString())
}
```
この問題もローカルで試しながら行った。
テリトリーとなる正方形を４つの位置のSetとして扱って、更にSetを使って何種類になるのかをカウントしている。
# 新都心のハイウェイ（paizaランク：A相当）
```kotlin
private const val CAR_A: Int = 0
private const val CAR_B: Int = -1
private const val GOAL: Int = -2
private const val WALL: Int = Int.MIN_VALUE
private val EMPTY: Int? = null

private class Cells(val h: Int, val w: Int, val cells: MutableList<Int?>) {
    operator fun get(x: Int, y: Int): Int? =
        if (x in 0 until w && y in 0 until h) {
            cells[y * w + x]
        } else {
            WALL // 範囲外は壁として扱う
        }

    operator fun set(x: Int, y: Int, value: Int?) {
        cells[y * w + x] = value
    }
}

private tailrec fun setupGoalInner(cells: Cells, x: Int, y: Int, dx: Int, dy: Int) {
    if (cells[x + dx, y + dy] != WALL) {
        cells[x + dx, y + dy] = GOAL
        setupGoalInner(cells, x = x + dx, y = y + dy, dx = dx, dy = dy)
    }
}

private fun setupGoal(cells: Cells) {
    // Bの位置を探す
    val indexB = cells.cells.indexOfFirst { it == CAR_B }
    val carBx = indexB % cells.w
    val carBy = indexB / cells.w
    // Bの位置から十字にゴール判定で埋める
    setupGoalInner(cells, carBx, carBy, dx = 1, dy = 0)
    setupGoalInner(cells, carBx, carBy, dx = -1, dy = 0)
    setupGoalInner(cells, carBx, carBy, dx = 0, dy = 1)
    setupGoalInner(cells, carBx, carBy, dx = 0, dy = -1)
}

private tailrec fun foundCarB(cells: Cells, step: Int): Int {
    var assigned = false
    for (y in 0 until cells.h) {
        for (x in 0 until cells.w) {
            if (cells[x, y] != step) continue
            assigned = true
            // ゴールに到達していたら終了
            if (cells[x + 1, y] == GOAL) return step + 1
            if (cells[x - 1, y] == GOAL) return step + 1
            if (cells[x, y + 1] == GOAL) return step + 1
            if (cells[x, y - 1] == GOAL) return step + 1
            // 次の移動ステップを代入
            if (cells[x + 1, y] == EMPTY) cells[x + 1, y] = step + 1
            if (cells[x - 1, y] == EMPTY) cells[x - 1, y] = step + 1
            if (cells[x, y + 1] == EMPTY) cells[x, y + 1] = step + 1
            if (cells[x, y - 1] == EMPTY) cells[x, y - 1] = step + 1
        }
    }
    // 代入がなかったら見つからないとして終了
    if (assigned.not()) return -1
    // 次
    return foundCarB(cells, step = step + 1)
}

private fun foundCarB(cells: Cells): Int {
    // Aの位置を取得
    val indexA = cells.cells.indexOfFirst { it == CAR_A }
    val carAx = indexA % cells.w
    val carAy = indexA / cells.w
    // ゴールを設定
    setupGoal(cells)
    // Aの位置のゴールチェック
    if (cells[carAx, carAy] == GOAL) return 0
    // 探す
    return foundCarB(cells, step = 0)
}

private fun line(): String = readLine()!!

fun main() {
    val (h, w) = line().split(" ").map { it.toInt() }
    val cells = (0 until h).map { line().toCharArray() }.flatMap { it.toList() }.map {
        when (it) {
            'A' -> CAR_A
            'B' -> CAR_B
            '#' -> WALL
            '.' -> EMPTY
            else -> error("Invalid cell: $it")
        }
    }.let { Cells(h, w, it.toMutableList()) }
    println(foundCarB(cells))
}
```
最初は、経路が見つからなかったときの処理を作成しなかったため、無限ループにしてしまっていました。
Aが移動する移動数を代入していく形をベースに求めようとしています。
指摘内容としては、効率が良くないようです。
そのうち、他の方の投稿なども見てみたいと思います。
# 思い出の屋上（paizaランク：S相当）
```kotlin
import kotlin.math.abs
import kotlin.math.max

private val EMPTY: Int? = null

private class Area(val h: Int, val w: Int) {
    val cells: MutableList<Int?> = MutableList(size = h * w) { EMPTY }

    operator fun get(row: Int, col: Int): Int? =
        if (row in 1..h && col in 1..w) {
            cells[(row - 1) * w + (col - 1)]
        } else {
            EMPTY // 範囲外は空として扱う
        }

    operator fun set(row: Int, col: Int, value: Int?) {
        check(this[row, col] == EMPTY) { "Already set. row=$row, col=$col, ${this[row, col]}" }
        if (row in 1..h && col in 1..w) {
            cells[(row - 1) * w + (col - 1)] = value
        }
    }

    override fun toString(): String {
        return buildString {
            for (row in 1..h) {
                for (col in 1..w) {
                    append(
                        when (val c = this@Area[row, col]) {
                            EMPTY -> '.'
                            else -> c!!.toString(radix = 16).last()
                        }
                    )
                }
                appendLine()
            }
        }
    }
}

private fun setupTerritory(area: Area, row: Int, col: Int, distance: Int) {
    for (dCol in -distance..distance) {
        val rowDistance = distance - abs(dCol)
        for (dRow in 0..rowDistance) {
            if (dRow == 0) {
                area[row, col + dCol] = distance
            } else {
                area[row + dRow, col + dCol] = distance
                area[row - dRow, col + dCol] = distance
            }
        }
    }
}

private fun canEstablishTerritory(area: Area, row: Int, col: Int): Int? {
    if (area[row, col] != EMPTY) return null
    val maxDistance = max(area.h, area.w)
    for (distance in 0..maxDistance) {
        for (dCol in -distance..distance) {
            val rowDistance = distance - abs(dCol)
            if (rowDistance == 0) {
                if (area[row, col + dCol] != EMPTY) return distance - 1
            } else {
                if (area[row + rowDistance, col + dCol] != EMPTY) return distance - 1
                if (area[row - rowDistance, col + dCol] != EMPTY) return distance - 1
            }
        }
    }
    return maxDistance
}

private fun findMaxTerritorySize(area: Area): Int =
    (1..area.h).flatMap { row -> (1..area.w).map { col -> Pair(row, col) } }
        .mapNotNull { (row, col) -> canEstablishTerritory(area, row = row, col = col) }
        .maxOrNull() ?: -1

private fun line(): String = readLine()!!

fun main() {
    val (h, w, m) = line().split(" ").map { it.toInt() }
    val area = Area(h, w)
    (1..m).map { line() }
        .map { line -> line.split(" ").map { it.toInt() } }
        .forEach { (row, col, distance) ->
            setupTerritory(area, row = row, col = col, distance = distance)
        }
    println(findMaxTerritorySize(area))
}
```
この問題もローカルで試しながら行った。
- - -
コツコツやってきましたが、プログラムの問題は全部記載したので、本投稿は終了です。
気が向けば解説の追記などを書くかもしれません。