結果

問題 No.5021 Addition Pyramid
ユーザー MulticolorWorld
提出日時 2025-02-25 21:04:33
言語 Kotlin
(2.1.0)
結果
RE  
実行時間 -
コード長 18,281 bytes
コンパイル時間 20,274 ms
コンパイル使用メモリ 565,156 KB
実行使用メモリ 58,024 KB
スコア 0
最終ジャッジ日時 2025-02-25 21:05:20
合計ジャッジ時間 39,872 ms
ジャッジサーバーID
(参考情報)
judge5 / judge4
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
other RE * 50
権限があれば一括ダウンロードができます

ソースコード

diff #

import java.io.*
import java.nio.file.Paths
import java.util.ArrayDeque
import kotlin.math.abs
import kotlin.math.max

fun next() = readLine()!!
fun nextInt() = next().toInt()
fun nextLong() = next().toLong()
fun nextDouble() = next().toDouble()
fun nextList() = next().split(" ")
fun nextIntList() = next().split(" ").map { it.toInt() }
fun nextLongList() = next().split(" ").map { it.toLong() }
fun nextDoubleList() = next().split(" ").map { it.toDouble() }

data class Coord(val x: Int, val y: Int)

data class CommuteInfo(val from: Coord, val to: Coord)

// 方向
val delta = arrayOf(Coord(1, 0), Coord(-1, 0), Coord(0, 1), Coord(0, -1))

enum class Env {
    Local,
    Prod
}

data class CalcResult(
    // インデックス
    val index: Int = 0,
    // 最短経路
    val path: List<Coord> = emptyList(),
    // 建築に必要なターン
    val buildTurn: Int = 0,
    // 1ターンあたりの利益
    val benefitPerTurn: Int = 0,
    // この経路の建設を開始するために必要な最小金額
    val buildableMinMoney: Int = 0,
    // この経路の建設を開始するために必要な最小ターン
    val buildableMinTurn: Int = 0,
    // 建てられるようになったら建てる、をした場合の最終ターンまでに出る利益、建てられない場合は0
    val benefitToLastTurn: Int = 0,
    // この経路を立てた場合に、新たに駅の範囲に入るインデックス
    val newConnected: List<BuildableOtherInfo> = emptyList()
)

// 既に建築されている駅の範囲に入っている端と、もう片方の端の情報
data class BuildableOtherInfo(
    val index: Int,
    val other: Coord,
)

// 建築された駅の情報
data class StationInfo(
    // 座標
    val coord: Coord,
    // 残っている建築可能な方向
    val directions: MutableList<Coord>,
)

fun solve() {
    val (N, M, K, T) = nextIntList()
    val commuteInfoList = mutableListOf<CommuteInfo>()

    // 端ともう片方の端の情報
    val coordInfo = Array(N) { Array(N) { mutableListOf<Pair<Int, Coord>>() } }
    for (i in 0 until M) {
        val (si, sj, ti, tj) = nextIntList()
        commuteInfoList.add(CommuteInfo(Coord(si, sj), Coord(ti, tj)))
        coordInfo[si][sj].add(Pair(i, Coord(ti, tj)))
        coordInfo[ti][tj].add(Pair(i, Coord(si, sj)))
    }

    // #: 通れない
    val wall = Array(N) { Array(N) { "" } }
    // 残りターン数
    var remainingTurn = T
    // 所持金額
    var money = K
    // 1ターンに増減する金額
    var moneyDelta = 0
    // 片方が既に建築されている駅の範囲にあるもう片方の座標
    val buildableOther = mutableListOf<BuildableOtherInfo>()
    // 既に建築されている駅の情報
    val stationInfo = mutableListOf<StationInfo>()
    // 既につながっているindex
    val connected = mutableSetOf<Int>()

    // 初期解生成
    val pathAndBenefit = calculatePathAndBenefit(M, N, commuteInfoList, T, money, coordInfo)
    val p = pathAndBenefit.filter { it.benefitToLastTurn != 0 && it.newConnected.size >= 2 }.maxByOrNull { it.benefitPerTurn }
    if(p != null) {
        println("# build start: ${p.index}")
        val path = p.path
        val pathString = convertPathToString(path)
        for (i in 1 until pathString.size - 1) {
            println(pathString[i])
        }
        println(pathString.first())
        println(pathString.last())

        for (c in path) {
            wall[c.x][c.y] = "#"
        }

        connected.add(p.index)

        // 駅情報の更新
        val startDirection = Coord(path[1].x - path[0].x, path[1].y - path[0].y)
        stationInfo.add(StationInfo(path[0], delta.filter { it != startDirection }.toMutableList()))
        val endDirection = Coord(path[path.size - 2].x - path[path.size - 1].x, path[path.size - 2].y - path[path.size - 1].y)
        stationInfo.add(StationInfo(path[path.size - 1], delta.filter { it != endDirection }.toMutableList()))

        // 駅範囲に入っている座標情報の更新
        buildableOther.addAll(p.newConnected)

        money -= p.buildableMinMoney
        moneyDelta += p.benefitPerTurn
        remainingTurn -= p.buildTurn

        println("# build end index: ${p.index}")
        println("# connected: ${connected.sorted().joinToString(", ")}")
        println("# remain: ${buildableOther.map { it.index }.sorted().joinToString(" ")}")
    }

    var reCalcFlag = false
    var result = calcB(N, remainingTurn, money, commuteInfoList, moneyDelta, buildableOther, stationInfo, wall, connected)

    while (remainingTurn > 0) {
        if(reCalcFlag){
            result = calcB(N, remainingTurn, money, commuteInfoList, moneyDelta, buildableOther, stationInfo, wall, connected)
            reCalcFlag = false
        }

        val p2 = result.sortedBy { it.benefitToLastTurn }.lastOrNull { it.benefitToLastTurn > 0 }
        if(p2 != null){
            if(p2.path.size == 1){
                connected.add(p2.index)
                reCalcFlag = true
                continue
            }
            println("# build start: ${p2.index}")
            if (p2.buildableMinTurn != 0) {
                repeat(p2.buildableMinTurn) {
                    money += moneyDelta
                    remainingTurn--
                    println(-1)
                }
            }
            val path = p2.path
            val pathString = convertPathToString(path)
            for (i in 1 until pathString.size - 1) {
                println(pathString[i])
            }
            println(pathString.last())

            for (c in path) {
                wall[c.x][c.y] = "#"
            }

            connected.add(p2.index)
            buildableOther.removeAll{ it.index == p2.index }

            stationInfo.find { it.coord == path[0] }?.directions?.remove(Coord(path[1].x - path[0].x, path[1].y - path[0].y))
            val endDirection = Coord(path[path.size - 2].x - path[path.size - 1].x, path[path.size - 2].y - path[path.size - 1].y)
            stationInfo.add(StationInfo(path[path.size - 1], delta.filter { it != endDirection }.toMutableList()))

            val newStation = path[path.size - 1]
            // 新しい駅範囲に入っている座標情報の更新
            for(i in -2 until 3) {
                for (j in -2 until 3) {
                    if (abs(i) + abs(j) <= 2) {
                        val x = newStation.x + i
                        val y = newStation.y + j
                        if (x in 0 until N && y in 0 until N) {
                            for (c in coordInfo[x][y]) {
                                val (index, other) = c
                                if (buildableOther.map { it.index }.contains(index)) {
                                    continue
                                }
                                if (!connected.contains(index)) {
                                    buildableOther.add(BuildableOtherInfo(index, other))
                                }
                            }
                        }
                    }
                }
            }

            money -= p2.buildableMinMoney
            moneyDelta += p2.benefitPerTurn
            remainingTurn -= p2.buildTurn
            reCalcFlag = true
            println("# build end index: ${p2.index}")
            println("# connected: ${connected.sorted().joinToString(", ")}")
            println("# remain: ${buildableOther.map { it.index }.sorted().joinToString(" ")}")
        } else {
            remainingTurn--
            money += moneyDelta
            println(-1)
        }
    }
}

// 駅の範囲に入っている座標のペアへの経路を探索し、最短経路や利益などの情報を計算する
fun calcB(
    N: Int,
    remainingTurn: Int, money: Int,
    commuteInfoList: List<CommuteInfo>,
    moneyDelta: Int, buildableOther: List<BuildableOtherInfo>,
    stationInfo: List<StationInfo>, wall: Array<Array<String>>,
    connected: MutableSet<Int>
): List<CalcResult>{
    val dist = Array(N) { IntArray(N) { -1 } }
    val before = Array(N) { Array(N) { Coord(-1, -1) } }
    val queue = ArrayDeque<Coord>()
    val endPoint = buildableOther.filter { !connected.contains(it.index) }.map { it.other }.toMutableList()
    for(s in stationInfo){
        dist[s.coord.x][s.coord.y] = 0
        for(d in s.directions){
            val dx = s.coord.x + d.x
            val dy = s.coord.y + d.y
            if (dx in 0 until N && dy in 0 until N) {
                if (dist[dx][dy] == -1) {
                    if (wall[dx][dy] == "") {
                        dist[dx][dy] = dist[s.coord.x][s.coord.y] + 1
                        before[dx][dy] = s.coord
                        queue.add(Coord(dx, dy))
                    }
                }
            }
        }
    }

    while (queue.isNotEmpty()) {
        val v = queue.removeFirst()
        if(endPoint.contains(v)){
            endPoint.remove(v)
            if(endPoint.isEmpty()){
                break
            }
            continue
        }

        for (d in delta) {
            val dx = v.x + d.x
            val dy = v.y + d.y
            if (dx in 0 until N && dy in 0 until N) {
                if (dist[dx][dy] == -1) {
                    if (wall[dx][dy] == "") {
                        dist[dx][dy] = dist[v.x][v.y] + 1
                        before[dx][dy] = v
                        queue.add(Coord(dx, dy))
                    }
                }
            }
        }
    }

    val result = mutableListOf<CalcResult>()
    for(e in buildableOther.filter { !connected.contains(it.index) }){
        if (dist[e.other.x][e.other.y] == -1) {
            continue
        }
        // 経路復元
        val path = mutableListOf<Coord>()
        var c = e.other
        while (dist[c.x][c.y] > 0) {
            path.add(c)
            c = before[c.x][c.y]
        }
        path.add(c)
        path.reverse()

        // 1ターンあたりの利益
        val benefitPerTurn = abs(commuteInfoList[e.index].from.x - commuteInfoList[e.index].to.x) +
                abs(commuteInfoList[e.index].from.y - commuteInfoList[e.index].to.y)
        // 建築に必要なターン
        val buildTurn = path.size - 1
        // この経路の建設を開始するために必要な最小金額
        val buildableMinMoney = (100 - moneyDelta) * (buildTurn - 1) + (5000 - moneyDelta) * 1 + moneyDelta
        // この経路の建設を開始するために必要な最小ターン
        val buildableMinTurn =
            if (buildableMinMoney <= money) {
                0
            } else if (moneyDelta == 0) {
                -1
            } else {
                (buildableMinMoney - money + moneyDelta - 1) / moneyDelta
            }
        // 建てられるようになったら建てる、をした場合の最終ターンまでに出る利益、建てられない場合は0
        val benefitToLastTurn =
            if (remainingTurn <= (buildTurn + buildableMinTurn)) {
                0
            } else if (buildableMinTurn == -1) {
                0
            } else {
                max(0, (remainingTurn - buildTurn - buildableMinTurn) * benefitPerTurn - (100 * (buildTurn - 2) + 5000))
            }

        result.add(CalcResult(e.index, path, buildTurn, benefitPerTurn, buildableMinMoney, buildableMinTurn, benefitToLastTurn))
    }
    return result
}

// 初期解を生成する
fun calculatePathAndBenefit(
    M: Int, N: Int,
    commuteInfoList: List<CommuteInfo>,
    T: Int, money: Int,
    coordInfo: Array<Array<MutableList<Pair<Int, Coord>>>>,
): Array<CalcResult> {

    val result = Array(M) { CalcResult() }

    for (i in 0 until M) {
        // 幅優先探索で最短経路を計算
        val dist = Array(N) { IntArray(N) { -1 } }
        val before = Array(N) { Array(N) { Coord(-1, -1) } }
        val queue = ArrayDeque<Coord>()

        val start = commuteInfoList[i].from
        val end = commuteInfoList[i].to

        queue.add(start)
        dist[start.x][start.y] = 0
        while (queue.isNotEmpty()) {
            val v = queue.removeFirst()
            if (v.equals(end)) {
                break
            }
            for (d in delta) {
                val dx = v.x + d.x
                val dy = v.y + d.y
                if (dx in 0 until N && dy in 0 until N) {
                    if (dist[dx][dy] == -1) {
                        dist[dx][dy] = dist[v.x][v.y] + 1
                        before[dx][dy] = v
                        queue.add(Coord(dx, dy))
                    }
                }
            }
        }

        // 経路復元
        val path = mutableListOf<Coord>()
        var c = end
        while (c != start) {
            path.add(c)
            c = before[c.x][c.y]
        }
        path.add(start)
        path.reverse()

        // 1ターンあたりの利益
        val benefitPerTurn = abs(start.x - end.x) + abs(start.y - end.y)
        // 建築に必要なターン
        val buildTurn = path.size
        // この経路の建設コスト
        val buildableMinMoney = 100 * (buildTurn - 2) + 10000
        // 最終ターンまでに出る利益、建てられない場合は0
        val benefitToLastTurn =
            if(money < buildableMinMoney){
                0
            }else{
                (T - buildTurn) * benefitPerTurn - buildableMinMoney
            }
        // 新たに範囲に入るindex
        val buildableOther = mutableListOf<BuildableOtherInfo>()

        for(s in listOf(path.first(), path.last())){
            for(j in -2 until 3) {
                for (k in -2 until 3) {
                    if (abs(j) + abs(k) <= 2) {
                        val x = s.x + j
                        val y = s.y + k
                        if (x in 0 until N && y in 0 until N) {
                            for (cc in coordInfo[x][y]) {
                                val (index, other) = cc
                                if (i != index) {
                                    buildableOther.add(BuildableOtherInfo(index, other))
                                }
                            }
                        }
                    }
                }
            }
        }


        result[i] = CalcResult(i, path, buildTurn, benefitPerTurn, buildableMinMoney, 0, benefitToLastTurn, buildableOther)
    }
    return result
}

fun convertPathToString(path: List<Coord>): List<String> {
    val result = mutableListOf<String>()

    for (i in path.indices) {
        if (i == 0) {
            result.add("0 ${path[i].x} ${path[i].y}")
            continue
        }
        if (i == path.lastIndex) {
            result.add("0 ${path[i].x} ${path[i].y}")
            continue
        }

        val from = path[i - 1]
        val now = path[i]
        val to = path[i + 1]

        if (abs(now.y - from.y) == 1 && abs(to.y - now.y) == 1) {
            result.add("1 ${now.x} ${now.y}")
        } else if (abs(now.x - from.x) == 1 && abs(to.x - now.x) == 1) {
            result.add("2 ${now.x} ${now.y}")
        } else if (now.y - from.y == 1 && to.x - now.x == 1) {
            result.add("3 ${now.x} ${now.y}")
        } else if (now.x - from.x == -1 && to.y - now.y == -1) {
            result.add("3 ${now.x} ${now.y}")
        } else if (now.y - from.y == 1 && to.x - now.x == -1) {
            result.add("4 ${now.x} ${now.y}")
        } else if (now.x - from.x == 1 && to.y - now.y == -1) {
            result.add("4 ${now.x} ${now.y}")
        } else if (now.x - from.x == 1 && to.y - now.y == 1) {
            result.add("5 ${now.x} ${now.y}")
        } else if (now.y - from.y == -1 && to.x - now.x == -1) {
            result.add("5 ${now.x} ${now.y}")
        } else if (now.x - from.x == -1 && to.y - now.y == 1) {
            result.add("6 ${now.x} ${now.y}")
        } else if (now.y - from.y == -1 && to.x - now.x == 1) {
            result.add("6 ${now.x} ${now.y}")
        }
    }
    return result
}

fun solveLocal() {
    val resourcePath = Paths.get("ahc043", "src", "main", "resources")
    val inDir = Paths.get(resourcePath.toString(), "in").toFile()
    val outDir = Paths.get(resourcePath.toString(), "out").toFile()
    if (!outDir.exists()) {
        outDir.mkdirs()
    }

    val resultFile = Paths.get(resourcePath.toString(), "result.csv").toFile()
    val resultFileWriter = FileWriter(resultFile, false)

    resultFileWriter.write("File, Score\n")
    resultFileWriter.flush()

    if (inDir.listFiles() != null) {
        for (inFile in inDir.listFiles()!!.sortedBy { it.name }) {
            val fileName = inFile.name
            val outFile = File(Paths.get(outDir.toString(), fileName).toString())
            if (!outFile.exists()) {
                outFile.createNewFile()
            }
            System.setIn(FileInputStream(inFile))
            System.setOut(PrintStream(outFile))
            val startTime = System.currentTimeMillis()
            solve()
            val endTime = System.currentTimeMillis()
            PrintStream(FileOutputStream(FileDescriptor.out)).println("solve end $fileName ${endTime - startTime}ms")
        }

        for (inFile in inDir.listFiles()!!.sortedBy { it.name }) {
            val fileName = inFile.name
            val outFile = File(Paths.get(outDir.toString(), fileName).toString())

            val pb = ProcessBuilder("cmd", "/c", ".\\vis.exe", ".\\in\\${inFile.name}", ".\\out\\${outFile.name}")
            pb.directory(resourcePath.toFile())
            pb.redirectErrorStream(true)
            val process = pb.start()
            process.waitFor()

            val br = BufferedReader(InputStreamReader(process.inputStream))
            val scoreString = br.readLine()
            br.close()

            PrintStream(FileOutputStream(FileDescriptor.out)).println(scoreString)
            val score = scoreString.split(" ")[2].toInt()
            resultFileWriter.write("$fileName, $score\n")
            resultFileWriter.flush()
        }
    }
}

fun main(args: Array<String>) {
    val env = if (args.isNotEmpty() && args[0] == "local") {
        Env.Local
    } else {
        Env.Prod
    }

    if (env == Env.Local) {
        solveLocal()
    } else {
        solve()
    }
}
0