不想看英文的题目?翻译在后面
Problem Statement |
|||||||||||||
Manao is traversing a valley inhabited by monsters. During his journey, he will encounter several monsters one by one. The scariness of each monster is a positive integer. Some monsters may be scarier than others. The i-th (0-based index) monster Manao will meet has scariness equal to dread[i]. Manao is not going to fight the monsters. Instead, he will bribe some of them and make them join him. To bribe the i-th monster, Manao needs price[i] gold coins. The monsters are not too greedy, therefore each value in price will be either 1 or 2. At the beginning, Manao travels alone. Each time he meets a monster, he first has the option to bribe it, and then the monster may decide to attack him. A monster will attack Manao if and only if he did not bribe it and its scariness is strictly greater than the total scariness of all monsters in Manao's party. In other words, whenever Manao encounters a monster that would attack him, he has to bribe it. If he encounters a monster that would not attack him, he may either bribe it, or simply walk past the monster. ![]() Consider this example: Manao is traversing the valley inhabited by the Dragon, the Hydra and the Killer Rabbit. When he encounters the Dragon, he has no choice but to bribe him, spending 1 gold coin (in each test case, Manao has to bribe the first monster he meets, because when he travels alone, the total scariness of monsters in his party is zero). When they come by the Hydra, Manao can either pass or bribe her. In the end, he needs to get past the Killer Rabbit. If Manao bribed the Hydra, the total scariness of his party exceeds the Rabbit's, so they will pass. Otherwise, the Rabbit has to be bribed for two gold coins. Therefore, the optimal choice is to bribe the Hydra and then to walk past the Killer Rabbit. The total cost of getting through the valley this way is 2 gold coins. You are given the vector <int>s dread and price. Compute the minimum price Manao will pay to safely pass the valley. |
|||||||||||||
Definition |
|||||||||||||
|
|||||||||||||
Constraints |
|||||||||||||
- | dread will contain between 1 and 20 elements, inclusive. | ||||||||||||
- | Each element of dread will be between 1 and 2,000,000,000, inclusive. | ||||||||||||
- | price will contain between the same number of elements as dread. | ||||||||||||
- | Each element of price will be either 1 or 2. | ||||||||||||
Examples |
|||||||||||||
0) | |||||||||||||
|
|||||||||||||
1) | |||||||||||||
|
|||||||||||||
2) | |||||||||||||
|
|||||||||||||
3) | |||||||||||||
|
This problem statement is the exclusive and proprietary property of TopCoder, Inc. Any unauthorized use or reproduction of this information without the prior written consent of TopCoder, Inc. is strictly prohibited. (c)2003, TopCoder, Inc. All rights reserved.
题目翻译:
Manao要走过一片树林,树林有怪兽,每个怪兽有一个正整数值的战斗力,和一个价格(1或2),Manao可以选择喂养怪兽(花费1或2个金币),或者直接走过去。Manao也有一个战斗力值——是他所喂养的怪兽的全体的战斗力之和。Manao遇到的第i个怪兽战斗力为dread[i],价格为price[i],如果dread[i]大于Manao当前的战斗力,Manao就必须喂养它,否则怪兽就会攻击他,喂养消耗的金币数为price[i](补充说明:初始Manao战斗力为0,所以必须喂养第一个怪兽)
求Manao穿过树林消耗的最少金币数
输入限制:
怪兽个数在1~20之间(包含1和20)
怪兽战斗力在1~2×10^9之间
price的值为1或2
price和dread的包含元素个数相同
题目解析:
考虑递归处理,设当前Manao战斗力为sum,遇到第idx个怪兽(从0开始编号),设从这个时候开始他最少要消耗f(sum,idx)个金币才能通过树林
如果sum>=dread[idx],有两种选择,喂养或者直接走过去。如果喂养,付出price[idx]个金币,同时战斗力增加到sum+dread[idx],然后就可以处理下一个怪兽了,这种选择对应的最少消耗金币数为price[idx] + f(sum + dread[idx], idx + 1);如果不喂养,那么直接处理下一个怪兽,这种选择对应的最少消耗金币数为f(sum, idx + 1)。我们只有这两种选择,所以f(sum,idx)=min(price[idx] + f(sum + dread[idx], idx + 1), f(sum, idx + 1))
如果sum<dread[idx], 只有喂养,f(sum, idx) = price[idx] + f(sum + dread[idx], idx + 1)
递归的边界是:如果idx >= 怪兽个数,f(sum, idx) = 0
(写着写着就感觉有点像算法导论动态规划那一章的装配线问题)
下面是代码:
纯粹的递归搜索:
(这里有个严重的效率问题,即宏中使用了函数,应该把define那行注释掉,具体见http://www.cnblogs.com/fstang/archive/2013/01/07/2849317.html ---补充于2013-01-07)
#include <string> #include <iostream> #include <vector> #include <algorithm> #include <map> using namespace std; #define min(a,b) ((a) < (b) ? (a) : (b)) class MonstersValley2{ public: int minimumPrice(vector <int> dread, vector <int> price){ return f(0, 0, dread, price); } int f(long long sum, int idx, vector <int> &dread, vector <int> &price){ if (idx >= dread.size()) { return 0; } if (sum >= dread[idx]) { int m = min(price[idx] + f(sum + dread[idx], idx + 1, dread, price), f(sum, idx + 1, dread, price)); return m; } else { int m = price[idx] + f(sum + dread[idx], idx + 1, dread, price); return m; } } };
//这个代码有一个超过200ms,有一个超时,其他为0ms或1ms...不能通过
带备忘录的递归搜索(避免重复计算)
(这里同样有严重的效率问题,即宏中使用了函数,应该把define那行注释掉,具体见http://www.cnblogs.com/fstang/archive/2013/01/07/2849317.html ---补充于2013-01-07)
#include <string> #include <iostream> #include <vector> #include <algorithm> #include <map> using namespace std; #define min(a,b) ((a) < (b) ? (a) : (b)) class Elem{ public: long long sum; int idx; Elem(long long s, int i){ sum = s; idx = i; } bool operator < (const Elem &rhs) const { return sum < rhs.sum || sum == rhs.sum && idx < rhs.idx; } }; class MonstersValley2{ public: map<Elem, int> S; int minimumPrice(vector <int> dread, vector <int> price){ S.clear(); return f(0, 0, dread, price); } int f(long long sum, int idx, vector <int> &dread, vector <int> &price){ map<Elem, int>::iterator it = S.find(Elem(sum, idx)); if (it != S.end()) { return it->second; } if (idx >= dread.size()) { return 0; } if (sum >= dread[idx]) { int m = min(price[idx] + f(sum + dread[idx], idx + 1, dread, price), f(sum, idx + 1, dread, price)); S.insert(make_pair(Elem(sum, idx), m)); return m; } else { int m = price[idx] + f(sum + dread[idx], idx + 1, dread, price); S.insert(make_pair(Elem(sum, idx), m)); return m; } } };
//有5个案例运行时间超过200ms,最长480ms,无超时,多数为0ms或1ms,通过
写了个main函数测试一下(要修改类代码,增加一个cnt成员变量(public),每次从表中查到就cnt++),这个案例就是之前超时的那个,有19个元素,得到结果是:
最小总花费为2,从表中查到的总次数cnt=131071,可见还是有一些重叠子问题的,虽然我感觉sum是很稀疏的,重叠的可能性不大
int main(int argc, char* argv[]) { int a[] = {1782688262, 895047095, 1625373870, 1009836949, 985560038, 1470346827, 296839142, 34727454, 413009041, 1114435639, 1692481802, 422406335, 795130000, 1455087504, 410389760, 961349143, 1693064512, 621415696, 98442513}; int b[] = {2, 2, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1}; MonstersValley2 m; cout << m.minimumPrice(vector<int>(a, a+sizeof(a)/sizeof(int)), vector<int>(b, b+sizeof(b)/sizeof(int))) << endl; cout << "cnt:" << m.cnt << endl; system("pause"); return 0; }
更细致的测试:统计每个递归层次的查表次数,结果如下:
cnt[0]:0
cnt[1]:0
cnt[2]:1
cnt[3]:2
cnt[4]:4
cnt[5]:8
cnt[6]:16
cnt[7]:32
cnt[8]:64
cnt[9]:128
cnt[10]:256
cnt[11]:512
cnt[12]:1024
cnt[13]:2048
cnt[14]:4096
cnt[15]:8192
cnt[16]:16384
cnt[17]:32768
cnt[18]:65536
cnt[19]:0
竟然都是2的幂,看到这里我也吃了一惊,为什么是这样?我再想想……
-----------------------------------------------------the following comments are added on 2013-01-04----------------------------------------------------
看到查表时不同元素的查找频率是不同的,由于map是用红黑树实现的,属于平衡二叉树,并不是最优的。有个叫最优平衡二叉树的算法,当每个元素查找频率不完全相同时,可以构造相应的二叉查找树,使得总的代价最小。只是想想,因为这个实现起来就复杂了,还要自己写二叉查找树……
===============================================补充=============================================
补充一个Java版本的,因为考虑到hashmap从查找的效率来讲比红黑树实现的map更好,但是C++11中才提供unordered_map,所以就试试Java,遗憾的是,这个也不够好,有个case还是fail了,超时,在我的电脑上跑是out of memory....
还是贴一下,main里就是那个fail的case
(感叹自己对Java很是不熟,写代码到处查,各种错误,感觉很不好,关于new的用法还老是和C++扯不清)
import java.util.HashMap; public class MonstersValley { private HashMap<Elem, Integer> map = new HashMap<Elem, Integer>(); public int minimumPrice(long[] dread, int[] price){ return f(0, 0, dread, price); //return 0; } int f(int idx, long sum, long[] dread, int[] price){ Integer res; if (idx >= dread.length) {//递归边界 return 0; } if (map.containsKey(new Elem(sum, idx))) {//查表 return map.get(new Elem(sum, idx)); } if (sum >= dread[idx]) { res = Math.min(f(idx + 1, sum, dread, price), price[idx] + f(idx + 1, sum + dread[idx], dread, price)); } else { res = price[idx] + f(idx + 1, sum + dread[idx], dread, price); } map.put(new Elem(sum, idx), res); return res; } public static void main(String args[]){ long dread[] = {8589934592l, 4294967296l, 2147483648l, 1073741824, 536870912, 268435456, 134217728, 67108864, 33554432, 16777216, 8388608, 4194304, 2097152, 1048576, 524288, 262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 170191697543L, 442151420049l, 760562175049l, 13784873423l, 178349965388l, 755005234214l, 541764950940l, 946482302586l, 543713207966l, 762391189316l, 871034340034l, 677925419895l, 160143387182l, 105796128936l, 529757177900l, 289213265278l}; int price[] = {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1}; MonstersValley m = new MonstersValley(); System.out.println(m.minimumPrice(dread, price)); } } class Elem{ public long sum; public int idx; public Elem(long s, int i){ sum = s; idx = i; } }