上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖
楼主: 李恒道 - 

3180. 执行操作可获得的最大总奖励 题解

[复制链接]
  • TA的每日心情
    擦汗
    2024-7-16 09:20
  • 签到天数: 192 天

    [LV.7]常住居民III

    709

    主题

    5867

    回帖

    6691

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6691

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 10 小时前 | 显示全部楼层 | 阅读模式

    来源于jager大佬的解释
    
    假如数组中最大的元素为k,而最优解中最大的元素为x,那么使用k替换x肯定符合要求,即k肯定大于所选的其他的元素的和,满足题目的要求。由于使用k替换x之后得到了一个更优的解,因此假设错误,从而证明最大的元素k一定在最优解中,否则不是最优解。
    
    根据以上我们可以证明数组一定包含最后一个元素,假设其值为k,则之前的累加和最大为k-1,则整个累加和最大范围为2k-1
    
    我们可以先对数组排序,然后生成一个二维dp表,行为每个数,列为累加和范围

        rewardValues = rewardValues.sort((a, b) => a - b)
        const dp = new Array(rewardValues.length).fill(0)
            .map(() => new Array(rewardValues[rewardValues.length - 1] * 2).fill(0))

    先考虑边界,首先累加和为0任何数字都可以满足
    第一行只有累加和为0以及累加和要求为第一个数字两种状态为真
    其他行则存在选和不选两种状态,如果不选则判断从0...i-1是否存在j的累加和
    假设数字为k
    如果选,则判断0...-1是否存在[j-k]的累加和
    由于存在累加和不得大于该数字,则如果选的话,最大的边界范围不得大于[k*2]这个边界
    而需要的累加和如果小于该数字,则无法选这个数字,只有不选一种选择
    可得选数字的范围为[k,2*k)

    var maxTotalReward = function (rewardValues) {
        rewardValues = rewardValues.sort((a, b) => a - b)
        const dp = new Array(rewardValues.length).fill(0)
            .map(() => new Array(rewardValues[rewardValues.length - 1] * 2).fill(0))
        dp[0][0] = true
        dp[0][rewardValues[0]] = true
        for (let index = 1; index < dp.length; index++) {
            for (let indey = 0; indey < dp[0].length; indey++) {
                dp[index][indey] = dp[index - 1][indey]
                if (indey >= rewardValues[index] && indey < 2 * rewardValues[index]) {
                    dp[index][indey] = dp[index][indey] || dp[index - 1][indey - rewardValues[index]]
                }
            }
        }
        for (let index = dp[0].length - 1; index >= 0; index--) {
            if (dp[dp.length - 1][index] == true) {
                return index
            }
        }
        return 0
    };

    最后我们直接判断最后一个数字,倒叙查找是否存在第一个为真返回即可
    

    基础优化

    我们可以看到每一行都转移了该上一行的数据,然后判断是否属于该数字的范围,如果属于则判断是否为真,并设置上
    那就可以进行状态压缩到一行
    需要注意的是遍历k数字的范围需要从大到小,如果从小到大容易出现本行的状态引用问题

    var maxTotalReward = function (rewardValues) {
        rewardValues = rewardValues.sort((a, b) => a - b)
        const dp = new Array(rewardValues[rewardValues.length - 1] * 2).fill(false)
        dp[0] = true
        dp[rewardValues[0]] = true
        for (let index = 1; index < rewardValues.length; index++) {
            const rewardValue = rewardValues[index]
            for (let indey = 2 * rewardValues[index] - 1; indey >= rewardValues[index]; indey--) {
                dp[indey] = dp[indey] || dp[indey - rewardValue]
            }
        }
        for (let index = dp.length - 1; index >= 0; index--) {
            if (dp[index] == true) {
                return index
            }
        }
        return 0
    };
    

    至此就实现了dp的推测和状态压缩

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表