https://coderun.yandex.ru/problem/del-to-max-subarray-sum-yandex Сложная

Решение

import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.BufferedWriter
import java.io.OutputStreamWriter

private const val NEG_INF: Long = -1000000000000000000L

fun main() {
    val reader = BufferedReader(InputStreamReader(System.`in`))
    val writer = BufferedWriter(OutputStreamWriter(System.out))
    val t = reader.readLine().toInt()
    repeat(t) {
        val (n, k) = reader.readLine().split(" ").map { it.toInt() }
        val a = reader.readLine().split(" ").map { it.toLong() }.toLongArray()
        writer.write("${solve(n, k, a)}\\n")
    }
    writer.flush()
    writer.close()
    reader.close()
}

private fun solve(n: Int, k: Int, a: LongArray): Long {
    var ans = NEG_INF

    // Если все элементы неположительные, ответ — максимум среди них.
    var maxA = a[0]
    for (i in 1 until n) {
        if(a[i] > maxA) maxA = a[i]
    }
    if (maxA <= 0L) {
        return maxA
    }

    // 1. Непокольцевой (не циклический) вариант решения.
    // dp[r] = максимум суммы подотрезка (конечного элемента текущей последовательности)
    // с использованием ровно r удалений (на этапе фильтрации).
    val dp = LongArray(k + 1) { NEG_INF }
    // Для начала не выбрано ни одного элемента: оставляем dp[*] = NEG_INF.
    // Обновляем dp при проходе по каждому документу.
    for (i in 0 until n) {
        // Обновляем для каждого возможного числа удалений от k до 0,
        // чтобы при обращении к dp[r-1] использовать значения предыдущей итерации.
        var r = k
        while (r >= 0) {
            if (r == 0) {
                dp[0] = maxOf(dp[0] + a[i], a[i])
            } else {
                dp[r] = maxOf(dp[r] + a[i], dp[r - 1])
            }
            if (dp[r] > ans) ans = dp[r]
            r--
        }
    }

    // 2. Расчёт префиксных сумм для циклического случая.
    // Здесь pref[i][r] = наилучшее значение суммы для отрезка, начиная с начала массива и заканчивая на позиции i-1,
    // с использованием ровно r удалений.
    val pref = Array(n + 1) { LongArray(k + 1) { NEG_INF } }
    val bestPref = Array(n + 1) { LongArray(k + 1) { NEG_INF } }
    pref[0][0] = 0L
    bestPref[0][0] = 0L
    for (r in 1..k) {
        pref[0][r] = NEG_INF
        bestPref[0][r] = NEG_INF
    }
    var i = 0
    while (i < n) {
        // Вычисляем pref[i+1][*] по отношению к pref[i][*]
        var r = 0
        // для r == 0
        pref[i + 1][0] = pref[i][0] + a[i]
        r = 1
        while (r <= k) {
            pref[i + 1][r] = maxOf(pref[i][r] + a[i], pref[i][r - 1])
            r++
        }
        // bestPref[i+1][r] = max(bestPref[i][r], pref[i+1][r], bestPref[i+1][r-1])
        r = 0
        while (r <= k) {
            bestPref[i + 1][r] = bestPref[i][r]
            if (pref[i + 1][r] > bestPref[i + 1][r]) {
                bestPref[i + 1][r] = pref[i + 1][r]
            }
            if (r > 0 && bestPref[i + 1][r - 1] > bestPref[i + 1][r]) {
                bestPref[i + 1][r] = bestPref[i + 1][r - 1]
            }
            r++
        }
        i++
    }
    // 3. Расчёт суффиксных сумм для циклического случая.
    // suff[i][r] = наилучшее значение суммы для отрезка, начинающегося с позиции i,
    // с использованием ровно r удалений, если двигаться к концу массива.
    val suff = Array(n + 1) { LongArray(k + 1) { NEG_INF } }
    val bestSuff = Array(n + 1) { LongArray(k + 1) { NEG_INF } }
    suff[n][0] = 0L
    bestSuff[n][0] = 0L
    for (r in 1..k) {
        suff[n][r] = NEG_INF
        bestSuff[n][r] = NEG_INF
    }
    i = n - 1
    while (i >= 0) {
        var r = 0
        suff[i][0] = suff[i + 1][0] + a[i]
        r = 1
        while (r <= k) {
            suff[i][r] = maxOf(suff[i + 1][r] + a[i], suff[i + 1][r - 1])
            r++
        }
        r = 0
        while (r <= k) {
            bestSuff[i][r] = bestSuff[i + 1][r]
            if (suff[i][r] > bestSuff[i][r]) {
                bestSuff[i][r] = suff[i][r]
            }
            if (r > 0 && bestSuff[i][r - 1] > bestSuff[i][r]) {
                bestSuff[i][r] = bestSuff[i][r - 1]
            }
            r++
        }
        i--
    }

    // 4. Объединяем префикс и суффикс для учета циклических (оборачивающихся) отрезков.
    // Будем считать точку разделения i от 1 до n-1, когда префикс берется из первых i элементов,
    // а суффикс — из оставшейся части.
    i = 1
    while (i < n) {
        var p = 0
        while (p <= k) {
            // p удалений используется в префиксе, оставшиеся (k-p) – в суффиксе.
            val cand = bestPref[i][p] + bestSuff[i][k - p]
            if(cand > ans) ans = cand
            p++
        }
        i++
    }

    return ans
}