问题
一辆载油500升的汽车从A开往1000公里外的B,已知汽车每公里耗油量为1升,A处有无穷多的油,其他任何地点都没有油,但该车可以在任何地点存放油以备中转,问从A到B最少需要多少油?
分析
这道题目初一看觉得方案太灵活,有点晕,网上所有对此题目的答案千篇一律的是:
题目可归结为求数列 an=500/(2n+1) n=0,1,2,3......的和Sn什么时候大于等于1000,解得n>6
当n=6时,S6=977.57
所以第一个中转点离起始位置距离为1000-977.57=22.43公里
所以第一次中转之前共耗油 22.43*(2*7+1)=336.50升
此后每次中转耗油500升
所以总耗油量为7*500+336.50=3836.50升
看起来很有道理,但是又没有说明为什么?害我抓破脑皮去想:这是为什么?怎么证明的?
不过我们简单推下:3836.50 - 7*(500 - 2*22.43) = 650.52,也就是说第一次中转跑7次会剩下650.52,证明这个答案是错误的!
后来我猜想出一种解释,首先我们需要抓住几个关键点:
1. 运输距离要尽量短,也就是说,我们的方案肯定是先从起点将所有需要的油搬到第一个x公里外的转运点,然后再以此类推,不能来回奔波于多个转运点。
2. 运输效率要尽可能高,也就是说,尽量以最大运输能力去跑——不要因为1、2公升油再跑一趟。
3. 相邻转运点之间跑的次数一定是奇数次。
由此,我们可以倒过来考虑:
1. 车到1000公里外的终点的时候,只需要放0的油在那里,转运次数1,可以求得距离是500;
2. 车到500公里外的转运点的时候,需要放500的油在那里,转运次数3,那么如何保证3次都是运输效率最高的呢?通过保证运运输效率最高,可以求得转运点的距离。上面也是这样求的。
3. 以此类推… 中转次数 1 3 5 7 …
解法一
/// <summary>
///计算在距离x的地方放a的油,跑2n+1次运输效率最高的距离
/// </summary>
double Distance(double a, int n)
{
return (tankLimit * (n + 1) - a) / (2 * n + 1);
}//x为距离,a = k(tankLimit - 2x) + 500 – x
/// <summary>
/// 在距离x的地方,存放amount的油需要跑的总次数
/// </summary>
int Trip(double x, double amount)
{
int n = 0;
while (true)
{
var a1 = 2 * n * (tankLimit - 2 * x);//来回运的油量
var a2 = tankLimit - x;//最后一次运的油量
var diff = a1 + a2 - amount;
if (diff >= 0 || -diff <= 0.0001) return 2 * n + 1;//避免计算误差
n++;
}
}
void Transport()//倒推中转
{
int k = 0;
double a = 0, d = 0;