公交车情况数问题

公交车问题

问题描述

一条线路上有 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 上获得。

posted @ 2018-07-10 15:06  brifuture  阅读(588)  评论(0编辑  收藏  举报