3180. 执行操作可获得的最大总奖励|
. - 力扣(LeetCode)
给你一个整数数组 rewardValues
,长度为 n
,代表奖励的值。
最初,你的总奖励 x
为 0,所有下标都是 未标记 的。你可以执行以下操作 任意次 :
- 从区间
[0, n - 1]
中选择一个 未标记 的下标i
。 - 如果
rewardValues[i]
大于 你当前的总奖励x
,则将rewardValues[i]
加到x
上(即x = x + rewardValues[i]
),并 标记 下标i
。
以整数形式返回执行最优操作能够获得的 最大 总奖励。
示例 1:
输入:rewardValues = [1,1,3,3]
输出:4
解释:
依次标记下标 0 和 2,总奖励为 4,这是可获得的最大值。
示例 2:
输入:rewardValues = [1,6,4,3,2]
输出:11
解释:
依次标记下标 0、2 和 1。总奖励为 11,这是可获得的最大值。
提示:
1 <= rewardValues.length <= 2000
1 <= rewardValues[i] <= 2000
这道题需要转变一下思路,因为一般来讲传统的动态规划呢都是直接计算出答案,这道题呢如果直接计算答案你会发现非常难写,那么我们就换一种思路,计算可达的状态。
还是按照背包问题的思路分为选或不选
不选的话就是dp[i][j] = dp[i - 1][j]
选的话要考虑清楚,首先 j 肯定要先大于reward,sum = j - reward,需要保证reward > sum,就推出了j < 2 * reward,至此动态转移方程就出来了
dp[i][j] = dp[i - 1][j]
if(j >= reward && j < 2 * reward) dp[i][j] = dp[i][j - reward] + reward
代码
class Solution {
static const int N = 2e3 + 10,M = 4e3 + 10;
bool dp[N][M];
public:
int maxTotalReward(vector<int>& rewardValues) {
int n = rewardValues.size();
if(n == 1) return rewardValues[0];
sort(rewardValues.begin(),rewardValues.end());
int v = rewardValues[n - 1] * 2;
dp[0][0] = true;
for(int i = 0;i < n;i ++){
for(int j = v;j >= 0;j --){
dp[i + 1][j] = dp[i][j];
if(j >= rewardValues[i] && j < 2 * rewardValues[i]){
dp[i + 1][j] = (dp[i + 1][j] || dp[i][j - rewardValues[i]]);
}
}
}
int res = 0;
for(int j = 0;j <= v;j ++){
if(dp[n][j]) res = max(res,j);
}
return res;
}
};
至于优化到一维dp的话就很简单了
class Solution {
static const int N = 2e3 + 10,M = 4e3 + 10;
bool dp[M];
public:
int maxTotalReward(vector<int>& rewardValues) {
int n = rewardValues.size();
if(n == 1) return rewardValues[0];
sort(rewardValues.begin(),rewardValues.end());
int v = rewardValues[n - 1] * 2;
dp[0] = true;
for(int i = 0;i < n;i ++){
for(int j = v;j >= 0;j --){
if(j >= rewardValues[i] && j < 2 * rewardValues[i]){
dp[j] = (dp[j] || dp[j - rewardValues[i]]);
}
}
}
int res = 0;
for(int j = 0;j <= v;j ++){
if(dp[j]) res = max(res,j);
}
return res;
}
};
3181. 执行操作可获得的最大总奖励| |
. - 力扣(LeetCode)
给你一个整数数组 rewardValues
,长度为 n
,代表奖励的值。
最初,你的总奖励 x
为 0,所有下标都是 未标记 的。你可以执行以下操作 任意次 :
- 从区间
[0, n - 1]
中选择一个 未标记 的下标i
。 - 如果
rewardValues[i]
大于 你当前的总奖励x
,则将rewardValues[i]
加到x
上(即x = x + rewardValues[i]
),并 标记 下标i
。
以整数形式返回执行最优操作能够获得的 最大 总奖励。
示例 1:
输入:rewardValues = [1,1,3,3]
输出:4
解释:
依次标记下标 0 和 2,总奖励为 4,这是可获得的最大值。
示例 2:
输入:rewardValues = [1,6,4,3,2]
输出:11
解释:
依次标记下标 0、2 和 1。总奖励为 11,这是可获得的最大值。
提示:
1 <= rewardValues.length <= 5 * 10^4
1 <= rewardValues[i] <= 5 * 10^4
那如果数据范围变大了,需要怎么考虑优化呢?首先我们已经推理出来了,在i >= reward && i < 2 * reward的条件下:dp[i] = dp[i] | dp[i - reward]
那么如果一个个去枚举在这种打范围的数据下会超时,考虑用每次计算得到的所有可达状态得到下一个可达状态。
就比如初始的时候可达状态一定是00000....1,因为当i == 0的时候一定可达。那如果第一个reward是3,那么首先考虑条件i >= reward && i < 2 * reward。
这里呢比较难想,在转换到二进制表示状态之后如何判断条件呢?
其实我们只需要看他是否会越界即可,怎么理解呢就比如我当前状态是00111表是0、1、2可达,那么如果当前reward = 3,我们把当前状态左移size = 5 - 3位在移回来,就变成了00011这样的话就已经去掉了状态3,因为状态3加2之后达到了5违反了:rewardValues[i]
大于 你当前的总奖励 x
这样的话呢就很清楚了,在条件判断完之后将当前状态左移reward位然后于原状态|一下即可
(位运算的相关介绍
位运算(&、^、补码、lowbit操作)-CSDN博客)
就得到了dp[i] = dp[i] | dp[i - reward]
代码
class Solution {
public:
int maxTotalReward(vector<int>& rewardValues) {
int n = rewardValues.size();
if(n == 1) return rewardValues[0];
sort(rewardValues.begin(),rewardValues.end());
rewardValues.erase(unique(rewardValues.begin(),rewardValues.end()),rewardValues.end());
bitset<100000> f{1};
for(int i = 0;i < n;i ++){
int v = rewardValues[i];
int d = f.size() - v;
f |= f << d >> d << v;
}
for(int i = rewardValues[n - 1] * 2 - 1;i >= 0;i --){
if(f.test(i)) return i;
}
return 0;
}
};
加油