问题描述:
问题描述
在一个游戏中,小W拥有 n
个英雄,每个英雄的初始能力值均为 1。她可以通过升级操作来提升英雄的能力值,最多可以进行 k
次升级。
每次升级操作包含以下步骤:
-
选择一个英雄
-
选择一个正整数
x
-
将该英雄的能力值 aiai 更新为:ai=ai+⌊ai/x⌋ai=ai+⌊ai/x⌋
- 其中
⌊ ⌋
表示向下取整操作
- 其中
游戏规则:
- 当英雄的能力值首次达到或超过目标值 bibi 时,小W可以获得对应的奖励 cici
- 每个英雄的奖励只能获得一次
- 升级操作的选择是自由的,可以多次选择同一个英雄进行升级
请计算在最多进行 k
次升级操作后,小W能获得的最大奖励总和。
测试样例:
样例1:
输入:
n = 4 ,k = 4 ,b = [1, 7, 5, 2] ,c = [2, 6, 5, 2]
输出:9
解释:可以通过以下操作获得最大奖励:
- 第一个英雄初始值为 1,已达到目标值 1,直接获得奖励2
- 第四个英雄初始值为 1,选择 x=1 升级一次变为 2,达到目标值2,获得奖励2
- 第三个英雄通过三次合理的升级操作 (1->2->4->5) 可达到目标值5,获得奖励5
总奖励为:2 + 2 + 5 = 9
样例2:
输入:
n = 3 ,k = 0 ,b = [3, 5, 2] ,c = [5, 4, 7]
输出:0
解释:无法进行升级操作,因此无法获得奖励
样例3:
输入:
n = 3 ,k = 3 ,b = [3, 5, 2] ,c = [5, 4, 7]
输出:12
解释:可以通过以下操作获得最大奖励:
可以通过合理的升级操作使第一个和第三个英雄达到目标值,获得总奖励5 + 7 = 12
解题思路:
我们首先提取题目的关键信息:
- 每个英雄初始能力值都是1,要升级到目标能力值,升级次数可能大于等于0(当目标能力值就是1的话不需要升级)。
- 当英雄升级到目标能力值时,可以获得对应的奖励。
- 我们需要求最多能获得多少奖励。
- 而我们只有k次升级机会。
基于上面几点,我们需要知道能力值升级到target能力值,最少可以几次升级。这样才能保证我们不浪费升级次数。我们来看一下升级公式:
ai = ai + ⌊ai/x⌋
其中x是正整数,也就是x至少得大于等于1
当x大于ai时,那么⌊ai/x⌋
就为0了,相当于没升级还浪费了一次升级机会。
当x等于ai时, 那么⌊ai/x⌋
就为1了,相当于升级了1能力。
当x小于ai时, 那么⌊ai/x⌋
最大可以为ai(相当于能力翻倍了),最小其实是1(由于是下取整)。
我们可以先计算出每个英雄升级到目标能力值最少花多少升级次数。 我们用f(i,target)表示当前能力值为i,升级到目标能力值target的最少升级次数 如果当前能力值翻倍还比目标能力值小的话,那就翻倍,答案为 1 + f(2 * i,target)。 如果翻倍比目标能力值大,那就取x从2开始往上试探,直到升级完的能力值是小于等于target的。,答案是1 + f(i + ⌊i/x⌋ ,target)。 这个过程我们还可以用记忆化搜索做一些优化。
此时我们已经算完了每个英雄升级到目标能力值花的最少次数了。 我们只需要在里面选一些,保证开销小于等于k,并且得到的奖励最多就行了。 其实这个就是一个背包问题了。
核心
-
两阶段问题:
- 能力值扩展:通过
comp2
求得从 1 到b[i]
的最小步数。 - 背包优化:利用这些步数作为重量解决 0-1 背包问题,最大化在不超过总步数的情况下的总价值。
- 能力值扩展:通过
-
算法策略:
- 记忆化搜索:用于优化能力扩展计算,减少重复计算。
- 动态规划:用于解决组合优化中的背包问题。
-
整体流程:
- 先转换问题为求步数的操作,然后将此步数与价值对应到一个背包问题中,再寻求在总步数不超过
k
时的最大价值。
- 先转换问题为求步数的操作,然后将此步数与价值对应到一个背包问题中,再寻求在总步数不超过
把升级能力当成背包容量,把奖励当成金额。以下提供具体的代码。 其中的01背包代码可以做优化,此处使用的是最朴素的01背包方法。
代码:
#include <unordered_map>
#include <vector>
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
unordered_map<int, int> memo;
int comp2(int curlevel,int target)
{
if(curlevel==target){
return 0;
}
int hashv = 31 * curlevel + target;
if(memo.find(hashv)!=memo.end()){
return memo[hashv];
}
if(2*curlevel<=target){
return memo[hashv]=1+comp2(2*curlevel,target);
}else{
int o=2;
for(;o<=curlevel;++o){
if((curlevel+ curlevel/o)>target){
continue;
}else{
break;
}
}
return memo[hashv]=1+comp2(curlevel+ curlevel/o,target);
}
}
int bpack(int k,const vector<int> &weight,const vector<int> &value){
//dp[i][j] 前i个物品里背包容量为j时最大的奖励
int n =weight.size();
vector<vector<int>> dp (n+1,vector<int>(k+1,0));
for(int i=1;i<n+1;++i){
for(int j=0;j<k+1;++j){
dp[i][j]=max(dp[i-1][j],j>=weight[i-1]?(dp[i-1][j-weight[i-1]]+value[i-1]):0);
}
}
return dp[n][k];
}
int solution(int n, int k, const std::vector<int>& b, const std::vector<int>& c) {
//对每个数达到的能力改成对应的开销次数
vector<int> times(b.size());
for(int i=0;i<n;++i){
times[i]=comp2(1,b[i]);
}
//把times当成每个物品的容量,c当初每个物品的价值,背包最大容量为k,01背包
return bpack(k, times, c);
}
int main() {
// Add your test cases here
std::cout << (solution(4, 4, {1, 7, 5, 2}, {2, 6, 5, 2}) == 9) << std::endl;
std::cout << (solution(3, 0, {3, 5, 2}, {5, 4, 7}) == 0) << std::endl;
return 0;
}
算法复杂度:
时间复杂度:O(n* log target + n* k)
空间复杂度:O(n * k),dp数组的大小