#!/usr/bin/env kotlin import java.util.Scanner val scanner = Scanner(System.`in`) class Map(private val grid: List>) { private val fields = mutableSetOf() private val map = mutableListOf>() init { for ((y, line) in grid.withIndex()) { map.add(line.mapIndexed { x, s -> Plot(Pair(x, y), s) }) } for (y in 0 until this.getHeight()) { for (x in 0 until this.getWidth()) { val plot = getPlot(x, y) if (!plot.hasField()) { val field = Field(this) plot.addField(field) fields.add(field) field.expand(plot) } } } } fun getWidth(): Int = grid[0].size fun getHeight(): Int = grid.size fun getPlot(x: Int, y: Int): Plot = map[y][x] fun getFields(): Set = fields fun getPrice(): Long = fields.sumOf { it.getPrice() } fun getBulkPrice(): Long = fields.sumOf { it.getBulkPrice() } } class Field(private val map: Map) { private val fieldPlots = mutableSetOf() private val borderPlots = mutableSetOf>() fun addPlot(plot: Plot) { fieldPlots.add(plot) } fun expand(plot: Plot) { val (x, y) = plot.getPosition() val checkPositions = listOf(Pair(x - 1, y), Pair(x + 1, y), Pair(x, y - 1), Pair(x, y + 1)) for ((checkX, checkY) in checkPositions) { if (checkX < 0 || checkX >= map.getWidth() || checkY < 0 || checkY >= map.getHeight()) { addBorder(plot, Plot(Pair(checkX, checkY), ".")) continue } val checkedPlot = map.getPlot(checkX, checkY) if (plot.getType() == checkedPlot.getType()) { if (!checkedPlot.hasField()) { checkedPlot.addField(this) expand(checkedPlot) } } else { addBorder(plot, checkedPlot) } } } private fun addBorder(plot: Plot, checkedPlot: Plot) { borderPlots.add(Pair(plot, checkedPlot)) } fun getPrice(): Long { return getArea() * getCircumference() } fun getArea(): Long = fieldPlots.size.toLong() fun getCircumference(): Long = borderPlots.size.toLong() fun getBulkPrice(): Long { return getArea() * getNumberOfSides() } private fun getNumberOfSides(): Long { val leftBorders = borderPlots.filter { it.first.getPosition().first > it.second.getPosition().first}.map { it.first.getPosition() } val rightBorders = borderPlots.filter { it.first.getPosition().first < it.second.getPosition().first}.map { it.first.getPosition() } val topBorders = borderPlots.filter { it.first.getPosition().second > it.second.getPosition().second}.map { it.first.getPosition() } val bottomBorders = borderPlots.filter { it.first.getPosition().second < it.second.getPosition().second}.map { it.first.getPosition() } return countSides(leftBorders) + countSides(rightBorders) + countSides(topBorders) + countSides(bottomBorders) } private fun countSides(coordinates: List>): Long { val visited = mutableSetOf>() val coordinateSet = coordinates.toSet() var sideCount = 0 val directions = listOf(Pair(1, 0), Pair(0, 1), Pair(-1, 0), Pair(0, -1)) fun dfs(coord: Pair) { val stack = mutableListOf(coord) while (stack.isNotEmpty()) { val current = stack.removeLast() if (current in visited) continue visited.add(current) directions.map { (dx, dy) -> Pair(current.first + dx, current.second + dy) } .filter { it in coordinateSet && it !in visited } .forEach { stack.add(it) } } } for (coord in coordinates) { if (coord !in visited) { sideCount++ dfs(coord) } } return sideCount.toLong() } } class Plot(private val position: Pair, private val type: String) { private var field: Field? = null fun addField(field: Field) { this.field = field field.addPlot(this) } fun hasField(): Boolean = field != null fun getPosition(): Pair = position fun getType(): String = type } val grid = mutableListOf>() while (scanner.hasNextLine()) { grid.add(scanner.nextLine().split("").filter { it.isNotEmpty() }) } val map = Map(grid.reversed()) println("The price is: ${map.getPrice()}") println("The bulk price is ${map.getBulkPrice()}")