3186. 施咒的最大总伤害(升级版)

https://leetcode.cn/problems/maximum-total-damage-with-spell-casting/description/
一个魔法师有许多不同的咒语。
给你一个数组\(power\),其中每个元素表示一个咒语的伤害值,可能会有多个咒语有相同的伤害值。
已知魔法师使用伤害值为\(power[i]\)的咒语时,他们就 不能 使用伤害为\(power[i] - 2\)\(power[i] - 1\)\(power[i] + 1\)或者\(power[i] + 2\)的咒语。

每个咒语最多只能被使用 一次 。

请你返回这个魔法师可以达到的伤害值之和的 最大值

示例 1:
输入:power = [1,1,3,4]
输出:6
解释:
可以使用咒语 0,1,3,伤害值分别为 1,1,4,总伤害值为 6 。

示例 2:
输入:power = [7,1,6,6]
输出:13
解释:
可以使用咒语 1,2,3,伤害值分别为 1,6,6,总伤害值为 13 。

提示:
\(1 <= power.length <= 10^5\)
\(1 <= power[i] <= 10^9\)

这个题和打家劫舍还有删除并获得点数是差不多的题,都是值域动态规划

但是这个题不能按照按照值域递推,因为值域的范围是1到1e9。

但是仔细思考一下,对于power[i]也是两种选择,选或者不选。
首先我们按照power[i]从小到大排序。

设置dp[i]为前i个最大值,状态转移方程就是:

  • 不选
    dp[i]=dp[i-1]

  • dp[i]=dp[j]+power[i],这个j就是i前面的第一个满足情况的,也就是abs(power[i]-power[j])>2。

思考一下这样做的弊端:
我们遍历这个j,一定是从i->1,遍历找到第一个满足条件的j。但是如果有很多相同的怎么办,这样肯定会超时的。
所以我们去重一下,但是还是要记录该数的个数。
之后:
\(dp[i]=max(dp[i-1],dp[j]+cnt[p[i]]*p[i])\),其中abs(power[i]-power[j])>2。

typedef long long ll;
class Solution {
public:
    long long maximumTotalDamage(vector<int>& power) {
        vector<pair<ll,ll>>v;
        int n=power.size();
        sort(power.begin(),power.end());
        v.push_back({-100,0});
        v.push_back({power[0],1ll});
        for(int i=1;i<n;i++){
            if(power[i]!=v[v.size()-1].first){
                v.push_back({power[i],1ll});
            }
            else{
                v[v.size()-1].second++;
            }
        }
        int m=v.size()-1;
        ll dp[100100];//前i个最大值
        for(int i=1;i<=m;i++){
            dp[i]=dp[i-1];//不选
            int j=i;
            while(j>=0){
                //cout<<v[j].first<<"-"<<v[i].first<<endl;
                if(abs(v[j].first-v[i].first)>2){
                    break;
                }
                else{
                    j--;
                }
            }
            dp[i]=max(dp[i],dp[j]+v[i].first*v[i].second*1ll);
        }
        return dp[m];
    }
};

posted @ 2024-06-17 15:51  lipu123  阅读(52)  评论(0)    收藏  举报