zoukankan      html  css  js  c++  java
  • 公交车情况数问题

    公交车问题

    问题描述

    一条线路上有 n 个公交车站,假设在到达第 i 个站点之前,车上总共有 x 个乘客,过了该站点后车上总共有 y 个乘客,
    司机会把 y-x 这个数字,即乘客数量变化值 d_i 记录下来,公交车有固定的载客量 g,若有一份司机的开车日志,车上乘客数量的可能情况有多少种

    输入输出

    输入: 第一行有两个数字 n 和 g,分别表示站点数量及当前撤了的最大载客数量(1 <= n,g <= 1000)

    第二行共 n 个数字,由空格分开,经过第 i 个公交车站后车上乘客的变化数量 d_i (-1000 <= d_i <= 1000)

    输入数据保证从总站出发时乘客数量大于等于 0

    输出:输出一个数字,即司机从总站出发时,车上乘客数量的可能性个数。

    样例

    // 样例输入一:
    4 10
    1 2 3 4
    // 样例输出一:
    1
    
    // 样例输入二:
    4 5
    2 -1 2 1
    // 样例输出二:
    2
    

    问题分析

    绘出示意图

    按照题中信息,可以画出如下的示意图:

    其中蓝色方框表示各个站点, di 即是第 i 站点的乘客数量变化,Pi 是经过第 i 个站点后车上的乘客数量。
    可以得到等式:

    • Pi - Pi-1 = di ,其中 0 < i <= n

    数学推导

    由于公交车的载客量为 g,那么对于每个 Pi 而言,都会存在默认的约束条件 0 < Pi < g


    简单情况:

    考虑等式:P1 - P0 = d1:

    对于减数 P0 而言,它的取值范围在

    • [P1min-d1, P1max-d1],

    由默认的约束条件得到取值范围:

    • [0, g-d1],

    实际上这个范围成立的条件是 d1 >= 0,而实际上 d1 可以取负值,所以 P0 的取值范围应该修正为

    • [max(0, -d1), min(g-d1, g)];

    对于被减数 P1 而言,同样可以得到其取值范围在

    • [max[0, d1], min(g+d1, g)]`。

    推广到一般情况:

    对于所有作为减数的 Pj (0 <= j < n),它的取值范围是

    • [max(0, -dj+1), min(g-dj+1, g)];

    而对于所有作为被减数的 Pk (0 < k <= n),它的取值范围是

    • [max(0, dk), min(g+dk, g)]。

    Pi-1 取最大范围 [0, g]

    在第 1 站到终点站之间的 Pi,即可以与 Pi-1 关联的等式中作为被减数,又可以与 Pi+1 关联的等式作为减数,因此是可以得到两个取值范围的,取其交集,得 Pi (0 < i < n)的范围是

    • [max(0, -di+1, di), min(g, g-di+1,, g+di)];

    特殊的如 P0 范围:[max(0, -d1), min(g-d1, g)]; 或 Pn 范围:[max(0, dn), min(g+dn, g)]


    从 P0 的范围开始迭代计算

    然而以上利用 Pi-Pi-1=di 对 Pi 范围进行的推导是建立在把 Pi-1 的取值范围当作静态的 [0, g] 而得出的 Pi 范围,实际上 Pi-1 的范围可能比静态的 [0, g] 要小。

    若已经求得 Pi-1 的范围是 [R1, R2] (0 <= R1 <= R2 <= g),那么作为被减数的 Pi 的取值范围应为:

    • [R1+di, R2+di]

    与上面得到的 Pi 的取值范围取交集,得到:

    • [max(0, R1+di, -di+1), min(g, R2+di, g-di+1)]

    由于 P0 的范围是确定的 [max(0, -d1), min(g-d1, g)],因此通过上式可以递归算出 Pi 的取值范围。

    结果推导

    通过数学分析推导,若得出 Pi 的范围为 [RiMin, RiMax],那么第 i 个站点后的人数可能情况就为

    RiMax - RiMin + 1

    取这些情况中值最小的那个,就是公交车经过整条路线时的人数变化的可能情况,也就是从总站出发时的人数可能情况。

    主要代码

    推导出数学公式后,编写代码就非常容易了。主要代码如下:

    int tmpMax = carSize, tmpMin = 0;
    
    // 算出 P0 的最小最大值
    person[0*2] = getMax(0, -diff[0]);
    person[0*2+1] = getMin(carSize, carSize - diff[0]);
    
    // 迭代算出 P1 - Pn-1 的最小最大值
    for(int i = 1; i < station; i++) {
        tmpMin = person[(i-1)*2];
        tmpMax = person[(i-1)*2+1];
        person[i*2]   = getMax( 0, tmpMin + diff[i-1], -diff[i] );
        person[i*2+1] = getMin( carSize, tmpMax + diff[i-1], carSize - diff[i] );
    }
    
    // 算出 Pn 的最小最大值
    tmpMin = person[(station-1)*2];
    tmpMax = person[(station-1)*2+1];
    person[station*2]   = tmpMin + diff[station-1];
    person[station*2+1] = tmpMax + diff[station-1];
    

    这段代码用到的最大最小值的求值公式就是由上述数学推导而来。

    其它的解法

    上述数学推导用到的方法是迭代求取范围,最开始思考这个题的时候的思路是假设 Pi-1 都取最大范围 [0, g],使用以下公式:

    遍历所有可能去到的 k 和 j,利用 Pj 的范围求出 Pk 的范围,相应的代码虽然能够解出,但是并不好理解,执行效率比前面所说的迭代方法低。以下是之前版本代码的主要部分:

    int tmp = 0, tmpMax = carSize, tmpMin = 0;
    tmp = carSize - d_i[0];
    if( tmp < carSize ) {
        person[1] = tmp;
    }
    
    for(int gap = 1; gap < station; gap++) {
        for(int i = gap; i < station + 1; i++) {
            tmpMax = person[(i-gap)*2+1] + personChanged(i-gap, i);
            tmpMin = person[(i-gap)*2]   + personChanged(i-gap, i);
    
            if( i < station) {
                // 最后一个计算的时候没有额外的约束条件了
                tmp = carSize - d_i[i];
                tmpMax = getMin(tmp, tmpMax);  // 取较小的(即取交集)
            }
    
            if( tmpMax < tmpMin ) {
                // 若上限小于下限,对应的不等式不成立,说明不存在这种情况
                cases = 0;
                return;
            }
    
            tmpMax = getMin(tmpMax, carSize);
            person[i*2+1] = getMin(person[i*2+1], tmpMax);
    
            tmpMin = getMax(0, tmpMin);
            person[i*2] = getMax(person[i*2], tmpMin);
        }
    }
    

    总结

    这个问题其实比较简答,思考的时候需要紧紧抓住 Pi 的定位,即它到底是作为减数还是被减数来求得范围的,否则就容易陷入思维误区,无法快速得到计算范围的公式。

    改进的版本原始的版本 都可以在 GitHub 上获得。

  • 相关阅读:
    为什么无法从外部访问VSTO对象?
    通过实例代码理解WPF的Dispatcher
    Silverlight打印解决方案2.0之如何自定义表体
    VSTO"无法加载自定义程序集"
    打开silverlight项目之前,您需要安装最新的Silverlight Developer运行时
    Android 3.2 联机测试adb驱动如何安装和配置?
    Android 3.2 应用程序联机(devices)测试失败提示INSTALL_FAILED_INSUFFICIENT_STORAGE
    Android sdk 3.0 sdk3.1 sdk3.2 平板开发环境安装日志
    android 蓝牙开发常见问题总结
    Pad本蓝牙模块检测
  • 原文地址:https://www.cnblogs.com/brifuture/p/9289144.html
Copyright © 2011-2022 走看看