https://coderun.yandex.ru/selections/2024-summer-mobile-dev/problems/dead-battery-2/description Сложная

Решение

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

fun main(args: Array<String>) {
    val reader = BufferedReader(InputStreamReader(System.`in`))
    val n = reader.readLine().toInt()
    val tokens = reader.readLine().split(" ")
    val arr = IntArray(n) { tokens[it].toInt() }
    var S: Long = 0
    var maxVal = 0
    for (v in arr) {
        S += v.toLong()
        if (v > maxVal) maxVal = v
    }
    val writer = BufferedWriter(OutputStreamWriter(System.out))
    
    // Случай n = 2 – единственное разбиение.
    if (n == 2) {
        val ans = (10000L / arr[0]) + (10000L / arr[1])
        writer.write(ans.toString())
        writer.flush()
        reader.close()
        writer.close()
        return
    }
    
    // Если S не превышает 20000 – размер маленький, можно делать "классическое" ДП.
    if (S <= 20000) {
        val SInt = S.toInt()
        val dp = BooleanArray(SInt + 1)
        dp[0] = true
        for (v in arr) {
            for (j in SInt downTo v) {
                if (dp[j - v]) {
                    dp[j] = true
                }
            }
        }
        var best = Long.MAX_VALUE
        for (s in 1 until SInt) {
            if (dp[s]) {
                val cand = (10000L / s) + (10000L / (SInt - s))
                if (cand < best) best = cand
            }
        }
        writer.write(best.toString())
        writer.flush()
        reader.close()
        writer.close()
        return
    } else {
        // S > 20000
        if (n == 3) {
            // Для n=3 перебираем все 3 варианта разбиения (каждый вариант – взять один элемент против двух остальных)
            var best = Long.MAX_VALUE
            for (i in 0 until 3) {
                val part = arr[i].toLong()
                val other = S - part
                if (part == 0L || other == 0L) continue
                val cand = (10000L / part) + (10000L / other)
                if (cand < best) best = cand
            }
            writer.write(best.toString())
            writer.flush()
            reader.close()
            writer.close()
            return
        } else {
            // Для n>=4 и S>20000.
            // Ограничим диапазон сумм, который нас интересует.
            // Пусть L = min( S//2, 10000 + maxVal ).
            // Если удаётся получить на одном устройстве сумму s ≥ 10001 (при условии s ≤ S–10001),
            // то оба устройства будут иметь расход больше 10000 и ⎣10000 / (>10000)⎦ = 0, ответ = 0.
            val half = (S / 2).toInt()
            val L = if (half < 10000 + maxVal) half else (10000 + maxVal)
            
            // Выполним ДП битовым способом для сумм от 0 до L.
            val size = (L + 64) / 64
            val dp = LongArray(size)
            dp[0] = 1L // нулевая сумма достижима
            
            for (v in arr) {
                // Если v > L, добавление v даст сумму выше L – нас не волнуют такие состояния.
                if (v > L) continue
                val shifted = shiftLeft(dp, v, L)
                for (i in 0 until size) {
                    dp[i] = dp[i] or shifted[i]
                }
            }
            // Для разбиения с обоими расходами > 10000 необходимо, чтобы на одном устройстве сумма s была ≥ 10001,
            // а другой – S – s ≥ 10001. При S > 20000 и S достаточно большом (например, при малых значениях a) условие s ≤ S–10001 выполняется автоматически,
            // если s ≤ L, поскольку L ≤ 10000+maxVal ≤ 20000.
            val upperBound = if (S - 10001 < L.toLong()) (S - 10001).toInt() else L
            var exists = false
            for (s in 10001..upperBound) {
                if (testBit(dp, s)) {
                    exists = true
                    break
                }
            }
            if (exists) {
                writer.write("0")
                writer.flush()
                reader.close()
                writer.close()
                return
            } else {
                // Иначе оптимальное злоумышленное распределение вынуждает минимум не превышать 10000.
                // Находим максимальную достижимую сумму m₀ в диапазоне [1, min(L, 10000)].
                val limit = if (10000 < L) 10000 else L
                var m0 = 0
                for (s in 1..limit) {
                    if (testBit(dp, s)) m0 = s
                }
                // Для устройства с расходом m₀ время = ⎣10000/m₀⎦, а второе устройство получит расход >10000 (время 0).
                val ans = 10000L / m0
                writer.write(ans.toString())
                writer.flush()
                reader.close()
                writer.close()
                return
            }
        }
    }
}

// Функция для проверки, установлен ли бит на позиции pos в битовой маске dp.
fun testBit(dp: LongArray, pos: Int): Boolean {
    val index = pos / 64
    val bit = pos % 64
    return (dp[index] and (1L shl bit)) != 0L
}

// Функция осуществляет побитовый сдвиг массива dp влево на shift бит,
// возвращая новый LongArray длины dp.size; при этом значения свыше L игнорируются.
fun shiftLeft(dp: LongArray, shift: Int, L: Int): LongArray {
    val n = dp.size
    val res = LongArray(n)
    val wordShift = shift / 64
    val bitShift = shift % 64
    for (i in 0 until n) {
        val srcIndex = i - wordShift
        var x = if (srcIndex in 0 until n) dp[srcIndex] shl bitShift else 0L
        if (bitShift != 0 && srcIndex - 1 in 0 until n) {
            x = x or (dp[srcIndex - 1] ushr (64 - bitShift))
        }
        res[i] = x
    }
    // Обнулим биты, которые выходят за позицию L.
    val totalBits = n * 64
    val excess = totalBits - (L + 1)
    if (excess > 0) {
        res[n - 1] = res[n - 1] and ((1L shl (64 - excess)) - 1)
    }
    return res
}