用“双优先队列”方法解决双/多指标的规划问题
PS:本文要解决的问题不算难度很大的问题,但“双优先队列”的方法是以前没有见过的,昨天睡觉突然想到的,以时间Ο(nlogn),空间O(n)的较好代价解决一类问题,记录一下。
问题抽象:
算法的设计中常会出现这种需求,一个对象有多种比较策略,而不同的比较往往会产生矛盾,目的是在一项指标的约束下实现另一指标的最优化。
描述的云里雾里,其实例子比较好理解,比方说,KFC的外卖小哥们每人有一个电驴子,不同的剩余电量不同,远距离的单需要电多的车子去送,而派出小哥剩的电越多,成本就越高(这个好像没道理。。先设想这个情景吧)。那么对于一批订单,怎么决定派出的外卖小哥能让成本最优呢?
分析问题:
看起来可以用贪心的思想解决。车子有两个指标:电量和成本,问题的难点在于,这二者往往是冲突的,不论从哪出发进行贪心都很难证明最优。合理的贪心策略描述为:对于订单距离需求大向小排序,总在剩余的满足当前最大需求(电量大于等于距离)的小哥中派出成本最低的。
解决方案:
采用两个优先队列,对外卖小哥定制两种类的表达:ElecFirstBro和CostFirstBro,CostFirstBro支持参数为const ElecFirstBro &的构造,重载比较符,使两个队列中分别以电量剩余优先和成本小优先保存数据。
先将小哥全存入ElecFirstBro的队列中。
对订单反向排序,从大到小迭代(因为在远距离中没有采用的小哥也许在近距离的比较中反而可以采用,所以应当由大到小比较,保留每个没采用的Bro)。每次迭代中,用循环将所有适用当前距离需求的全出队,同时加入CostFirstBro队列。调整完毕后,从CostFirstBro弹出队首(显然是成本最小的)。
1 struct ElecFirstBro 2 { 3 int elec; 4 int cost; 5 }; 6 bool operator< (const ElecFirstBro &_B1, const ElecFirstBro &_B2) { return _B1.elec < _B2.elec; } 7 8 struct CostFirstBro 9 { 10 int elec; 11 int cost; 12 CostFirstBro(const ElecFirstBro &_B) :elec{ _B.elec }, cost{ _B.cost } {} 13 }; 14 bool operator< (const CostFirstBro &_B1, const CostFirstBro &_B2) { return _B1.cost > _B2.cost; } 15 16 class Solution 17 { 18 int minimum_cost(const vector<int> &_Require, const vector<ElecFirstBro> &_Bros) 19 { 20 priority_queue<ElecFirstBro> epq; 21 for (const auto &bro : _Bros) 22 { 23 epq.push(bro); 24 } 25 priority_queue<CostFirstBro> cpq; 26 int sum = 0; 27 for (const auto req : _Require) 28 { 29 while (!epq.empty() && epq.top().elec >= req) 30 { 31 cpq.push(CostFirstBro(epq.top())); 32 } 33 if (cpq.empty()) 34 { 35 return -1; 36 } 37 else 38 { 39 sum += cpq.top().cost; 40 cpq.pop(); 41 } 42 } 43 return sum; 44 } 45 };