小Q打算穿越怪兽谷,他不会打怪,但是他有钱。他知道,只要给怪兽一定的金币,怪兽就会一直护送他出谷。
在谷中,他会一次遇见N只怪兽,每只怪兽都有自己的武力值和要“贿赂”它所需的金币。如果小Q没有“贿赂”它而它的武力值又高于护送小Q的所有怪兽的武力值之和,这只怪兽就会攻击小Q
小Q想知道,要想成功穿越怪兽谷而不被攻击,最少要准备多少金币?
输入描述:
第一行:输入一个整数N,代表怪兽的数量
第二行:输入N个整数:d1、d2、....dN,代表对应怪兽的武力值
第三行:输入N个整数:p1、p2、.....pN,代表收买对应怪兽所需要的金币
(1≤N≤501\leq N \leq501≤N≤50、1≤d1,d2,....dn≤10121\leq d_1,d_2,....d_n\leq 10^{12}1≤d1,d2,....dn≤1012、1≤p1,p2,....pn≤21\leq p_1,p_2,....p_n\leq 21≤p1,p2,....pn≤2)
输出描述:
一个整数,表示所需要的最小金币数量
一、思路
因为怪兽是否攻击你,取决于你所拥有的的怪兽武力值的总和,因此我们应该注重每只怪兽的性价比:
例如:一只怪兽的武力值为20,需要1金币,另一只武力值为30,需要2金币,明显第一只的性价比要高一些
因为第一只怪兽,花1金币就能买到20武力值,而第二只怪兽,花1金币只能买到15武力值
那么全买性价比高的有没有问题呢?
当然有,因为性价比高的不一定都在前面,如果都在后面,那么之前的钱该出的还是得出
实际上在第二行里面已经给出每个关卡所需要的最小武力值之和
所以策略应该是:
(1)找出武力值最高的怪兽,并以该武力值作为购买上限,当你所拥有的的武力值之和大于或者等于这个上限时,不必购买
(2)计算每只怪兽的性价比,当你所购买的武力值还没有到达(1)中的上限时,考虑购买性价比高的怪兽:
但是这一步实际上也有点问题:
你需要考虑购买之后,武力值是否超过了上限,实际情况还会更加复杂
那么我们换个角度来看待这个问题,之前我们的想法是根据常识来设计一些复杂的算法,以解决这个问题。
实际上N=50,如果尝试使用暴力解法,按照排列组合来看,时间复杂度最高也不过是O(250)O(2^{50})O(250),如果我们能够在其中加入一些条件,还可以大幅降低这个数值
(一)回溯算法
说起暴力解法,最适合的应该是回溯法了,每次遇到怪兽,我们有两种选择:
(1)收买
(2)不收买(有的关卡必须收买,没得选,比如:第一只怪兽)
我们可以设置一个武力值上限 和 最小金币来作为筛选最终结果的条件
C++代码:(没有找到OJ进行测试,可能有错,不过我觉得思路应该没有问题,如果有错,欢迎指正)
class Solution {
public:
int minMoney(vector<int>& Force_value, vector<int>& Need_money) {
int max_force = 0, current_pos = 0, current_force = 0, current_money = 0;
int sum = 0;
for (int i = 0; i < Force_value.size(); i++) {
if (Force_value[i] > max_force)
max_force = Force_value[i];
sum += Need_money[i];
}
// 初值:假设每个怪兽都要收买
int min_money = sum;
recall(Force_value, Need_money, current_pos, current_force, current_money, min_money, max_force);
return min_money;
}
void recall(vector<int>& Force_value, vector<int>& Need_money, int current_pos, int current_force, int current_money, int& min_money, int& max_force) {
if (current_force >= max_force) {
if (current_money < min_money)
min_money = current_money;
return;
}
// 开始闯关
int k = 0;
for (int i = current_pos; i < Force_value.size(); i++) {
if (current_force < Force_value[i]) { // 当前武力值低于怪兽,必须收买
current_force += Force_value[i];
current_money += Need_money[i];
// 如果目前为止,所花金币已经超过之前找到最小值,不用回溯,可以直接返回
if (current_money >= min_money)
return;
//不需要回溯
}
else { // 可以选择收买,也可以不收买。
// 不收买则什么都不用做
current_force += Force_value[i];
current_money += Need_money[i];
// 如果目前为止,所花金币已经超过之前找到最小值,不用回溯,可以直接返回
if (current_money >= min_money)
return;
recall(Force_value, Need_money, i + 1, current_force, current_money, min_money, max_force);
// 回溯,选择不收买
current_force -= Force_value[i];
current_money -= Need_money[i];
}
}
}
};