卡车运油一题的求解

这是天涯上很久以前的一个帖子,多年前求解过,但是具体过程写得不好,自己都看得都晦涩了。所以重新写一遍。

原题目:假设有一辆车,它的油箱恰好和一个油桶一样大,而且车上恰好可以运载一个油桶。假设一桶油可以让车开一百公里。现在在起点,车装满了油,另外起点还有100桶油。问,这车通过运油并途中自加油的方式,最远能离开起点多远?

首先简化一下题目,一桶油可以走一个单位长度(不用100公里,这样后面容易折算一些)
从某一个途中点A(i)运油到下一个途中点A(i+1),可作以下考虑:
1、把车辆的油箱里的油也计入,出发点A(i)有x桶油(x可以为小数),下一个加油点A(i+1)有y桶油(y也可以是小数)
2、为保证最大化运油效率,卡车从A(i+1)返回A(i)时,油箱留存的一点点油应该恰好用完,设距离为s,因此单程的油量也是s
3、总的往返次数为2m+1,其中返回次数为m,由1、2可得,x-y=(2m+1)*s。这里2m是往返m次,1是最后一次前往第i+1点。
4、前m次去,肯定要把油装满,即2桶油,去的路上用掉了s桶油,然后再装s桶油返回第i点,所以可以推论出:前m次实际运到的油量为(2-2*s)*m。(此处隐含了一个条件 s < 1)
5、最后一次前往第i+1点时,第i点剩下的油为 x-2m,然后把这剩下的油拖到第i+1点后,最后一次能运到第i+1点的油量为 x-2m-s。
6、考虑一种意外的情况,即到最后一次,由于第i点剩余的油量不足s了,就不值得回去了。这样是不是会因此而浪费一部分油呢?其实不会,如果出现这种情况,就应该缩小s,以满足最后一次的效率。当然最好的方案是,最后一次也能拉满,即也拉上 2桶油,即 x-2m = 2。
7、思考到这里,发现每次循环中 s 的设定,只有满足 x-2m = 2是效率最高的,即每个途中点的油量必须是 2的整数倍数。
8、由于起点(定义为第1点)的油量为101,不是2的整数倍数。所以在第2个点就必须设法将其凑整为2的整数倍数,最近的一个数就是100。这样,第一个关键问题,就是要用1的油耗量,让s尽可能的远。根据分析3,1=(2m+1)*s,其中m=50(因为先得把100桶油运走)。故 s=1/101。
9、  继续考虑到第3点,运到第3点98个油量,总油耗为2=(2m+1)*s,其中m=48,解得s=2/97
10、继续考虑到第4点,运到第4点96个油量,总油耗为2=(2m+1)*s,其中m=47,解得s=2/95
11、继续考虑到第5点,运到第4点94个油量,总油耗为2=(2m+1)*s,其中m=46,解得s=2/93
12、以此类推,\sum s=\frac{1}{101}+\frac{2}{97}+\frac{2}{95}+\frac{2}{93}+....+\frac{2}{5}+\frac{2}{3}+2,其中最后一个 2,是最后一次用2桶油不跑往返,直接跑直线的距离。上式使用电子表格可计算得5.86524。

以上的分析8,不一定是最优方案。以前用模拟退火,算到过5.87,但是具体算法还要再检查一下,再继续发布。

13、现在回到分析6,重新考虑 x 与 m之间的关系(暂不考虑 s)。根据分析4,当 x 不是 2的整数倍数时,必有 2m + z = x,其中 z < 2。因此 m=int(\frac{x}{2})int是取整函数。而x是是 2的整数倍数时,根据分析6,有 2m+2 = x,即 m=\frac{x}{2}-1

现在进入模拟退火的算法思考:

1、如分析8-12,设定了包含49个运油点,下面不拘泥于运油点只有49个。假设有P个运油点,每个运油点油量为p_{i},从上一个点到当前运油点的抵达路程为s_{i}。所有P个运油点的p_{i}s_{i}构成了一条路径(route),现在就要求一个路径(route),使\sum s_{i}能有最大值。
2、先“随意”预定义一条路径,然后对路径进行随机微调,依据模拟退火的判决原则,决定微调结果是否能接受,反复进行这样的随机微调,直至模拟退火到达目标
3、随机微调的方式,包括下面几种调整方式
3.1 随机选择一个运油点,微调(增或减)油量,相当于移动其距离位置
3.2 在路径中随机增加或减少一个运油点
3.3 为保证增、减的微动变量相对稳定,应设定一个最小微动变量,即最小油量变化量。上述两种变化,都必须保证在相邻的两个路径点之间满足最小油量变化量。当然这个最小油量变化量也就是本算法能达到的最小精度。毕竟不是求解析解,只是在求数值解。

# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function
import random
import math
import copy

# 最小油量变化量
global Oil_Step
Oil_minStep = 0.00003 # 这个参数决定了每次的微变量大小
#Boltzmann_constant=1.3806505*10^(-23) #kB

def One_CircleCount (x):
# 某一段的往返次数,x 为起始油量 (见 分析13)
    m = int(x/2)
    if m *1.0 == x/2 :
        # 如果 x 是2的整数倍
        m = x/2 - 1
    return m

def One_Distance (x, y):
# 某一段的运送距离,x 为起始油量, y 为运到油量 (见 分析3 )
    m = One_CircleCount (x)
    L = (x - y) / (2*m + 1)
    return L

def Route_Distance (Route):
    Distance = 0
    for i in range(1,len(Route)):
        Distance += One_Distance(Route[i-1],Route[i])
    return Distance

def Make_Next_Route (Route):    
    Not_New_Route_Generate = True
    while Not_New_Route_Generate:
        mRand = random.randint (1,4)
        if mRand == 1 : 
        # 1 右移动某个路径点(左边方向是起点,右边是终点)
            m = random.randint(1,len(Route)-2)
            #移动谁,不能是第一点101,也不能是最后一点2
            if Route[m]-Oil_minStep > Route[m+1]:
                if One_Distance (Route[m-1],Route[m]-Oil_minStep) < 1.0:
                    Route[m] = Route[m]-Oil_minStep
                    Not_New_Route_Generate = False
        elif mRand == 2 : 
        # 2 左移动某个路径点
            m = random.randint(1,len(Route)-2)
            if Route[m]+Oil_minStep < Route[m-1]:
                if One_Distance (Route[m]+Oil_minStep, Route[m+1]) < 1.0:
                    Route[m] = Route[m]+Oil_minStep
                    Not_New_Route_Generate = False
        elif mRand == 3 :
        # 3 增加一个路径点
            m = random.randint(0,len(Route)-2) # 在第m点后增点
            if Route[m+1]-Route[m] > Oil_minStep:
                Route.insert(m+1, Route[m]+(Route[m+1]-Route[m])/2)
                Not_New_Route_Generate = False
        else:
        # 4 减去一个路径点
            m = random.randint(1,len(Route)-2)
            if One_Distance (Route[m-1], Route[m+1]) < 1.0:
                del Route[m]
                Not_New_Route_Generate = False

def MainRun ():
    random.seed()
    Route = []
    Step_Value = 101
    # 以下生成一条路径list,路径上每个点的油量差为 1
    # list中存储的是运到的油量
    # 第0点油量 101, 最后1点油量 2
    while Step_Value > 2:
        Route.append(Step_Value)
        Step_Value = Step_Value - 0.001 # 这个参数是一个奇葩,越小,初始的总距离越远
    Route.append(2)
    R_Distance = Route_Distance(Route)
    """
    for i in range(0,len(Route)):
        print (Route[i],)
    print ("")
    """    
    T = 200
    # 估计最远距离为 5-6 之间,而初始距离 3.8,那么每次的微变量 dE 瞎猜最大也就是0.2
    # 根据 exp(-dE/T) 的判决式
    # 把模拟退火起点温度 2,温度每次下降 0.001,温度下降到 0.001,完成退火
    cc = 0
    while T > 1:
        print ("\n%d \t %f \t %e" %(cc, T, 2+R_Distance) ,end="",flush=True)
        cc += 1
        T -= 0.001
        j = 20
        while j > 0:
            # 内循环用来模拟同一温度下多次的状态转移,也称为抽样稳定准则
            # (为什么称为“抽样稳定”准则,我的理解是等温过程在邻域选择下
            # 一次状态转移,一般需要在邻域内采样多次,进行多次状态转移,
            # 进而达到这一温度下的稳定状态)
            tmp_R = copy.copy(Route)
            Make_Next_Route (tmp_R)
            tmp_R_Distance = Route_Distance(tmp_R)
            delta_Distance = R_Distance - tmp_R_Distance
            if delta_Distance < 0:
                # 新目标值更优
                j -= 1
                Route = tmp_R
                R_Distance = tmp_R_Distance
                print ("|", end="", flush=True)
            elif delta_Distance == 0:
                # 新目标没有变化,本次循环无效
                pass
            else:
                # 新目标值较劣,delta_Distance >0
                # 使用 metropolis principle
                j -= 1
                k = 10000000000 # 奇葩的优化参数,根据数据变化趋势进行手动调整
                BozF = - delta_Distance*k / T
                #try:
                Boz = math.exp (BozF)
                BozRand = random.random()
                print ("*", end=" ", flush=True)
                if (BozRand < Boz) :
                    Route = tmp_R
                    R_Distance = tmp_R_Distance
                    print ("_", end=" ", flush=True)
    print ("%d \t %f \t %e" %(cc, T, R_Distance))
    for i in range(1,len(Route)):
        print ("第 %d 段: " %(i), end="")
        print ("距离: %e," % (One_Distance(Route[i-1],Route[i])), end="")
        print ("往返次数: %d," % (2*One_CircleCount(Route[i-1],Route[i])+1),end="")
        print ("搬运油量: %e" % (Route[i]))

if __name__ == '__main__':
    MainRun()


##1使用随机数发生器产生一个随机的分子构型。 
##2对此分子构型的其中粒子坐标做无规则的改变,产生一个新的分子构型。 
##3计算新的分子构型的能量。 
##4比较新的分子构型于改变前的分子构型的能量变化,判断是否接受该构型。 
##若新的分子构型能量低于原分子构型的能量,则接受新的构型,使用这个构型重复再做下一次迭代。 
##若新的分子构型能量高于原分子构型的能量,则计算玻尔兹曼因子,并产生一个随机数。 
##	若这个随机数大于所计算出的玻尔兹曼因子,
##  则放弃这个构型,重新计算。 x1 := exp(-delta_distance*k/T);
##	k = 1.3806505(24) × 10^23 J/K
##	若这个随机数小于所计算出的玻尔兹曼因子,则接受这个构型,使用这个构型重复再做下一次迭代。 
##5如此进行迭代计算,直至最后搜索出低于所给能量条件的分子构型结束。 
#@ignore

代码中的几个参数需要手动调整,详见注释。简单优化一下参数,可以得到5.885451的结果。
但是似乎每次运油点的间隔越小,总距离就越大。

其中的解析解的数学推导可能会涉及到变分和泛函,一直也没有精力去进一步学习研究了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值