当青训营遇上码上掘金

53 阅读5分钟

当青训营遇上码上掘金

看不懂证明过程可以直接阅读源码和样例分析哦~

题目:寻友之旅

小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。 步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1 公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走) 请帮助小青通知小码,小青最快到达时间是多久? 输入: 两个整数 N 和 K 输出: 小青到小码家所需的最短时间(以分钟为单位)

题目可抽象为如下

×2代表坐公交车一次,+a代表走a(a可以小于0)K=(...((N+a0)×2+a1)×2+a2)...)×2+an    T=n+i=0nan(K,N,T,nN,a0,a1,a2,...,anZ,0N,K100000)(考虑实际意义(走到负数位置是没有意义的),式①中的计算过程量均应为自然数)已知K,N,求T的最小值\begin{array}{l} (\times2代表坐公交车一次,+a代表走a步(a可以小于0)) \\ K=(...((N+a_0)\times 2+a_1)\times 2+a_2)...)\times 2+a_n \space\space\space\space\cdot\cdot\cdot① \\ T=n+\sum\limits_{i=0}^{n}|a_n| \\ (K,N,T,n\in N,且a_0,a_1,a_2,...,a_n\in Z,且0≤N , K≤100 000) \\ (考虑实际意义(走到负数位置是没有意义的),式①中的计算过程量均应为自然数) \\ 已知K,N,求T的最小值 \end{array}

思路分析

(缩小ak的范围)设方程①的解为A=(a0,a1,a2,...,an)T则方程①必存在另一解A=(a0,a1,a2,...,ak+1,ak+12...,an)T且方程①必存在另一解A=(a0,a1,a2,...,ak1,ak+1+2...,an)T(其中k0)此时TT=akak+1+ak+1ak+12此时TT=akak1+ak+1ak+1+2ak+12    则TT一定大于0,即T<T    则解A比解A更优ak+12    则TT一定大于0,即T<T    则解A比解A更优综上所述A为最优解,则k[1,n],ak{1,0,1}    逆向推导÷2表示曾上过一次公交车,a表示曾走过a步)方程①可转化为N=((...(Kan)...)÷2a2)÷2a1)÷2a0    设数组{Kn}首项为K0=K,通项公式为Kn=(Kn1an)÷2N=Kna0,且{Kn}N  ({Kn}的实际意义为每次上公交车的位置)为求得最优解A,由②得k[1,n],ak{1,0,1}(Kn的可能的取值个数)Kn=(Kn1an)÷2{Kn}Zk[1,n],ak{1,0,1}易证:若Kk12个差为1的可能的取值,则Kk也有2个差为1的可能的取值易证:若Kk1只有1个可能的取值,则Kk只有1个可能的取值 或 2个差为1的可能的取值所以k[1,n],Kk只有1个可能的取值 或 2个差为1的可能的取值所以Kn只有1个可能的取值 或 2个差为1的可能的取值(Kk的范围)k[0,n1],Kk=2,i[1,nk],Kk+i=1 (原地踏步)Kk=2Kk+1=1的时间花费为1,与走路相同,所以没必要坐公交车所以,对于{Kn},若Kk=2,则n=k(实际意义为,如果曾坐公交车到达2的位置,那么没必要)所以k[0,n],Kk2(求已知Kn的最大值)Kn以最小的速度递减,即Kn=(Kn1+1)÷2Kn=2,解得n=log(K1)所以n的最大值为log(K1)综上,解题思路为(n的所有可能×Kn的所有可能=答案的所有可能)K0=KKn=(Kn1an)÷2{Kn}Zk[1,n],ak{1,0,1}逐个递推算出K0Klog(K1),若算出2则下一次结束循环并在递推计算的过程中计算T的值,保留T的历史最低记录即答案最坏时间为log(K1)×2,即时间复杂度为O(logK)补充特例:if NK,直接T=NKelse if K1,那么只有K=1,N=0这种情况,直接T=1else 按上面的方法计算\begin{array}{l} \mathbf{(缩小a_k的范围)} \\ 设方程①的解为\mathbf{A}=(a_0,a_1,a_2,...,a_n)^T \\ 则方程①必存在另一解\mathbf{A'}=(a_0,a_1,a_2,...,a_k+1,a_{k+1}-2...,a_n)^T \\ 且方程①必存在另一解\mathbf{A''}=(a_0,a_1,a_2,...,a_k-1,a_{k+1}+2...,a_n)^T \\ (其中k\ge0) \\ 此时T-T'=|a_k|-|a_k+1|+|a_{k+1}|-|a_{k+1}-2| \\ 此时T-T''=|a_k|-|a_k-1|+|a_{k+1}|-|a_{k+1}+2| \\ 若a_{k+1}\ge2 \\ \space\space\space\space则T-T'一定大于0,即T'<T \\ \space\space\space\space则解A'比解A更优 \\ 若a_{k+1}\le-2 \\ \space\space\space\space则T-T''一定大于0,即T''<T \\ \space\space\space\space则解A''比解A更优 \\ 综上所述 \\ 若A为最优解,则\forall k\in[1,n],a_k\in\{-1,0,1\} \space\space\space\space\cdot\cdot\cdot② \\ \\ \mathbf{逆向推导} \\ (\div2表示曾上过一次公交车,-a表示曾走过a步) \\ 方程①可转化为N=((...(K-a_n)...)\div 2-a_2)\div 2-a_1)\div 2-a_0 \space\space\space\space\cdot\cdot\cdot③ \\ 设数组\{K_n\}首项为K_0=K,通项公式为K_n=(K_{n-1}-a_n)\div 2 \\ 则N=K_n-a_0,且\{K_n\}\in N\space\space(\{K_n\}的实际意义为每次上公交车的位置) \\ 为求得最优解A,由②得\forall k\in[1,n],a_k\in\{-1,0,1\} \\ \\ \mathbf{(K_n的可能的取值个数)} \\ 由K_n=(K_{n-1}-a_n)\div 2、\{K_n\}\in Z、\forall k\in[1,n],a_k\in\{-1,0,1\} \\ 易证:若K_{k-1}有2个差为1的可能的取值,则K_k也有2个差为1的可能的取值 \\ 易证:若K_{k-1}只有1个可能的取值,则K_k只有1个可能的取值\space或\space2个差为1的可能的取值 \\ 所以\forall k\in[1,n],K_k只有1个可能的取值\space或\space2个差为1的可能的取值 \\ 所以K_n只有1个可能的取值\space或\space2个差为1的可能的取值 \\ \\ \mathbf{(K_k的范围)} \\ \forall k\in [0,n-1],当K_k=2时,\forall i\in [1,n-k],K_{k+i}=1\space(原地踏步) \\ 且K_k=2到K_{k+1}=1的时间花费为1,与走路相同,所以没必要坐公交车 \\ 所以,对于\{K_n\},若K_k=2,则n=k(实际意义为,如果曾坐公交车到达2的位置,那么没必要) \\ 所以\forall k\in [0,n],K_k\ge2 \\ \\ \mathbf{(求已知K时n的最大值)} \\ 让{K_n}以最小的速度递减,即K_n=(K_{n-1}+1)\div 2 \\ 令K_n=2,解得n=log(K-1) \\ 所以n的最大值为⌈log(K-1)⌉ \\ \\ \mathbf{综上,解题思路为} \\ (n的所有可能\times K_n的所有可能=答案的所有可能) \\ 由K_0=K、K_n=(K_{n-1}-a_n)\div 2、\{K_n\}\in Z、\forall k\in[1,n],a_k\in\{-1,0,1\} \\ 逐个递推算出K_0到K_{⌈log(K-1)⌉},若算出2则下一次结束循环 \\ 并在递推计算的过程中计算T的值,保留T的历史最低记录即答案 \\ \\ 最坏时间为⌈log(K-1)⌉\times2,即时间复杂度为O(logK) \\ \\ \mathbf{补充特例:} \\ if\space N\ge K,直接T=N-K \\ else\space if\space K\le 1,那么只有K=1,N=0这种情况,直接T=1 \\ else\space 按上面的方法计算 \end{array}

代码展示

# 代码如下:
from math import log

# 输入略
N = int(input())
K = int(input())
if N >= K:
    T = N - K
    print(T)
elif K <= 1:
    # 那么只有K=1,N=0这种情况
    T = 1
    print(T)
else:
    nMax = int(log(K - 1)) + 1
    K1 = [0] * nMax
    K2 = [0] * nMax
    T1 = [0] * nMax
    T2 = [0] * nMax
    # 声明数组K1[log(K-1)],K2[log(K-1)]记录n每个取值下Kn的两种可能,初始化为0
    # 计算中令每个元素的K1<K2,或者只有1种则K2记为0
    # 声明数组T1[log(K-1)],T2[log(K-1)]分别记录从K1和K2到达K所需花费的总时间,初始化为0
    K1[0] = K
    T1[0] = 0
    for i in range(1, nMax):
        # 计算K1[i]和K2[i]和T1[i]和T2[i]
        if K2[i - 1] != 0 and K1[i - 1] % 2 == 0 and K2[i - 1] % 2 != 0:
            K1[i] = K1[i - 1] // 2  # ==(K2[i-1]-1)//2这是另一种到达K1[i]的方式
            T1[i] = min(T1[i - 1] + 1, T2[i - 1] + 2)  # 两种到达K1[i]的方式,取最优
            K2[i] = (K2[i - 1] + 1) // 2
            T2[i] = T2[i - 1] + 2
            if T1[i] - T2[i] > 1:  # 互相的距离不能过大
                T1[i] = T2[i] + 1
            if T2[i] - T1[i] > 1:  # 互相的距离不能过大
                T2[i] = T1[i] + 1
        elif K2[i - 1] != 0  and  K1[i - 1] % 2 != 0  and  K2[i - 1] % 2 == 0:
            K1[i] = (K1[i - 1] - 1) // 2
            T1[i] = T1[i - 1] + 2
            K2[i] = K2[i - 1] // 2  # ==(K1[i-1]+1)//2这是另一种到达K1[i]的方式
            T2[i] = min(T2[i - 1] + 1, T1[i - 1] + 2)  # 两种到达K1[i]的方式,取最优
            if T1[i] - T2[i] > 1:  # 互相的距离不能过大
                T1[i] = T2[i] + 1
            if T2[i] - T1[i] > 1:  # 互相的距离不能过大
                T2[i] = T1[i] + 1
        elif K2[i - 1] == 0 and K1[i - 1] % 2 == 0:
            K1[i] = K1[i - 1] // 2  # 只有一种可能
            T1[i] = T1[i - 1] + 1
            K2[i] = T2[i] = 0
        elif K2[i - 1] == 0 and K1[i - 1] % 2 != 0:
            K1[i] = (K1[i - 1] - 1) // 2  # K1变为K1K2两种可能
            T1[i] = T1[i - 1] + 2
            K2[i] = (K1[i - 1] + 1) // 2
            T2[i] = T2[i - 1] + 2
            if T1[i] - T2[i] > 1:  # 互相的距离不能过大
                T1[i] = T2[i] + 1
            if T2[i] - T1[i] > 1:  # 互相的距离不能过大
                T2[i] = T1[i] + 1
        if K1[i] == 2 or K2[i] == 2:  # 终止条件
            break
    Tmin = K - N
    for i in range(1, nMax):
        if K1[i] == 0:
            break
        Tmin = min(Tmin, abs(N - K1[i]) + T1[i])
        if K2[i] != 0:
            Tmin = min(Tmin, abs(N - K2[i]) + T2[i])
    print(Tmin)

举例分析

N=5,K=101则

  1. 初始化记录K1[0]=101,T1[0]=0

  2. 从K1[0]=101可以到(101-1)/2=50和(101+1)/2=50,这个操作需要花费时间2

    • 所有可达位置为50和51,总花费为T1[0]+2=2和T1[0]+2=2
    • 记录K1[1]=50,K2[1]=51,T1[1]=2,T2[1]=2
  3. 从K1[1]=50可以到50/2=25,这个操作需要花费时间1,

    从K1[1]=51可以到(51-1)/2=25和(51+1)/2=26,这个操作需要花费时间2

    • 所有可达位置为25和26,总花费为min(T1[1]+1,T2[1]+2)=3和T2[1]+2=4
    • 记录K1[2]=25,K2[2]=26,T1[2]=3,T2[2]=4
  4. 从K1[2]=25可以到(25-1)/2=12和(25+1)/2=13,这个操作需要花费时间2,

    从K1[2]=26可以到26/2=13,这个操作需要花费时间1

    • 所有可达位置为12和13,总花费为T1[2]+2=5和min(T1[2]+2,T2[2]+1)=5
    • 记录K1[3]=12,K2[3]=13,T1[3]=5,T2[3]=5
  5. 从K1[3]=12可以到12/2=6,这个操作需要花费时间1,

    从K1[3]=13可以到(13-1)/2=6和(13+1)/2=7,这个操作需要花费时间2

    • 所有可达位置为12和13,总花费为min(T1[3]+1,T2[3]+2)=6和T1[3]+2=7
    • 记录K1[4]=6,K2[4]=7,T1[4]=6,T2[4]=7
  6. 从K1[4]=6可以到6/2=3,这个操作需要花费时间1,

    从K1[4]=7可以到(7-1)/2=3和(7+1)/2=4,这个操作需要花费时间2

    • 所有可达位置为6和7,总花费为min(T1[4]+1,T2[4]+2)=7和T1[4]+2=9
    • T2[5]-T1[5]>1,而从K1[5]=3走到K2[5]=4只需要1分钟,所以令T2[5]=8
    • 记录K1[5]=3,K2[5]=4,T1[5]=7,T2[5]=8
  7. 从K1[5]=3可以到(3-1)/2=1和(3+1)/2=2,这个操作需要花费时间2,

    从K1[5]=4可以到4/2=2,这个操作需要花费时间1

    • 所有可达位置为1和2,总花费为min(T1[5]+1,T2[5]+2)=9和T1[5]+2=9
    • 记录K1[6]=1,K2[6]=2,T1[6]=9,T2[6]=9
  8. K2[6]==2循环结束

  9. 最终结果为

    • K1=[101, 50, 25, 12, 6, 3, 1, 0]
    • K2=[0, 51, 26, 13, 7, 4, 2, 0]
    • T1=[0, 2, 3, 5, 6, 7, 9, 0]
    • T2=[0, 2, 4, 5, 7, 8, 9, 0]
  10. 遍历求abs(K1[i]-N)+T1[i]和abs(K2[i]-N)+T2[i]的最小值

    • abs(K1[i]-N)为还需要步行的距离
    • T1[i]为已经花费的时间
    • K2,T2同理
  11. 最小值结果为abs(K1[4]-N)+T1[4]=6-5+6=7

    • 回推步骤分析结果
    • ((((101-1)/2+0)/2-1)/2+0)/2-1=5
    • 即((((((5+1)*2)+0)*2+1)+0)*2+0)*2+1=101花费7分钟