对测试最后一公里的思考

   作为一名入行也有些年头的测试人员,今天想来跟各位同行(无论你是开发,PM,还是测试or其它职位上的“挨踢”人员)一起探讨下最后一公里的问题,只是我这个最后一公里探讨的对象是作为“测试工程师”存在于各大公司,包括在国内BAT这样的大IT公司里面也为数不少的一群人。

  不记得在哪里看到这么一个桥段,桥段说:在恋爱的季节期,一年有365天,如果你在其中的某一天做的不好,那么尽管其余的364天你做的都很优秀也等于0,那一天的失误也将直接导致你364天付出的归零。感觉好苦逼的样子,看来马司令说的恋爱容易,婚姻不易,且行且珍惜,恋爱也不一定说就容易哦.......

   作为一名普通的测试人员,也辗转了几家公司。每一处,都非常强调测试人员的能力提升。比如说:coding能力、框架设计能力、DB能力、架构理解能力、code review能力等等云云,全方位,360度打造我们测试界的“克塞号,铠甲勇士,葫芦娃”.......当然,我对于这样能力提升的安排是举我所能及的手赞成的,并且我非常同意,如果要把测试做好,这些能力绝对是你不能忽视的,否则真会只见一斑。看不到实质,抓不住重点。但是在这条路上,有时候我们是不是丢失了一些什么?弄错了一些什么?方向是不是也开始偏了?我们作为测试人员,我们有最后一公里吗?如果有,我们的最后一公里是什么?

  首先,我们看个例子:有这么一个测试团队,就职于国内某大型IT(绝对的大型)企业,很多人毕业想进都很难的,记得当初自己进来也是面了几次才勉强获得机会。在平常的工作中,组内不仅仅有专门的人去负责和维护单元测试,也有专门的人去开发测试工具,更有专人去做协议测试,破解,暴力测试(请相信我,绝对不是简单的点点,比如里面有PDB文件的破解、反编译,反汇编)、弱网络等等测试,对的,这些都是测试人员干的。该团队所在的质量中心,也有专门的团队在做外包人员考核,分析。专门的团队负责大数据平台的建设,设计,开发、dump文件分析等等。总之,绝对是一只战斗力惊人的团队,是你居家休闲,出门旅游的必备团队,为你解忧~~~~但是,就是这么的一只团队,这么的一个中心,在长跑的过程中,他们的最后一公里是怎么样的呢?请看真实回放:

A:我靠,什么情况?这个bug怎么会在外网出现?

B:MB,知道了。那边测试的时候环境被人清过DB,数据跟外网DB数据不一致,所以导致引发该问题的条件数据不存在。但是外网的DB里面有。。。

A:MLGB,紧急更新吧

......

A:我擦,为毛每次都是凌晨5点下班啊,是要做好床前明月光,猝死算工伤的节奏吗。。。。

B:你抱怨个飞机啊,那边打包出问题了,在调试呢?

A:为毛每次都要做的打包还是会出问题啊,,,是陨石砸中了巨坑在这里出现吗?

B:你懂个球,打包是每周不同的人打的,项目组那边很忙,没人做文档,有文档也是很久之前的了,可以作为贵司博物馆藏品

.......

A:怎么会这样?不可能啊,我绝对测过,没有这个问题的

B:淡定,问题查到了,是因为运维那边没有执行DB操作,淡定

A:为毛开服,外网发布不做一个外网发布方案啊,多好

B:你去推啊,只要你推的动,反正我们是推不动

A:去就去

A:你们能自己整个外网发布方案不?这样也好在外网发布之前将思想理顺,每个环节自己检查自己的,都OK之后也不会出现因发布某个环节遗漏导致的问题啊

C:恩,很对。恩,我们接受

.......过了很久,还是一样,,,木有文档,问题依旧,木有文档,问题依旧


A:纳尼?qio do ma dei?这不科学,为啥会有空指针?

B:你看过代码扫描工具的报告吗?没有发现?

A:没有发现

B:你看过代码来吗?没有发现

A:我表示真没发现

B:你测试过这种场景吗?

A:我测试过,正常的,我求你别问了,我真不知道,你告诉我吧,让我不断完善自己成为金刚葫芦娃,嘀嗒嗒嘀嘀嗒嗒,葫芦娃~~~

.........

朋友,你看到了什么?我可以说,这些都是真实的案例,都是团队加班了很久,忙碌了几天,最后一天外放之前甚至加班到凌晨3、5点,经常可以稍微坐坐就能看日出的时间才下班,走出公司的大门。。。当他们从疲惫中醒过来,迎接他们的不是sexy的钢管舞女郎,更不是达买的吉泽小姐,而是一堆的XTX内部沟通消息,“外网出bug了,这里有bug,有测试在吗?.......求紧急更新,测试看下啊?拜托~~~测试上去看看,预发布好了”每每这时,我都很想说一句:MD,我信了你的邪~~~兄弟们,对不起,我们,,,又倒在了最后一公里这里,,,

   那么到底是什么让我们总是屡屡的倒在最后一公里呢?往上看(只列举部分)

测试环境不受测试控制

项目组核心流程风险控制不严格

项目组对于测试建议,,,一直都在当个屁,即使出了问题,他也会带着耳塞说,没事,放个屁吗?反正音乐这么大,没人听得到

测试组平常做的一些工具开发,一些看起来很美的东西,可能真的只是很美,对于项目的帮助到底在哪里?还是仅仅只是给上面的老大年底的时候漂亮的PPT上再多增几笔?KPI的完成上再锦绣一番?

   好吧,说多无益,说多了就感觉像个抱怨的怨妇了,我只想说,我们测试发展到今天,平常真的很苦,很苦,为什么总是越过火焰山,穿过怒风林,马上要取到真经的时候,总是在最后一公里看到很多的尸体,真的很可惜,“佛祖”一直看不到,听不到,,,,,我们的声音在笑,泪在飘,项目组的你们看不到,看不到~~~

    请仔细思考,测试最后一公里到底是什么让他们倒下?作为测试行业的引导者,作为一个公司的测试部门leader,作为一个公司的CEO,副总裁,对于质量部门,你的预期期望到底该是什么?


内容概要:本文围绕“基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究”展开,提出了一种结合Koopman算子理论与递归神经网络(RNN)的数据驱动建模方法,旨在对非线性纳米定位系统进行有效线性化建模,并实现高精度的模型预测控制(MPC)。该方法利用Koopman算子将非线性系统映射到高维线性空间,通过递归神经网络学习系统的动态演化规律,构建可解释性强、计算效率高的线性化模型,进而提升预测控制在复杂不确定性环境下的鲁棒性与跟踪精度。文中给出了完整的Matlab代码实现,涵盖数据预处理、网络训练、模型验证与MPC控制器设计等环节,具有较强的基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)可复现性工程应用价值。; 适合人群:具备一定控制理论基础Matlab编程能力的研究生、科研人员及自动化、精密仪器、机器人等方向的工程技术人员。; 使用场景及目标:①解决高精度纳米定位系统中非线性动态响应带来的控制难题;②实现复杂机电系统的数据驱动建模与预测控制一体化设计;③为非线性系统控制提供一种可替代传统机理建模的有效工具。; 阅读建议:建议结合提供的Matlab代码逐模块分析实现流程,重点关注Koopman观测矩阵构造、RNN网络结构设计与MPC控制器耦合机制,同时可通过替换实际系统数据进行迁移验证,深化对数据驱动控制方法的理解与应用能力。
<think>题目要求计算出租车的最小花费,乘客可以选择将行程分成多段,每段独立计费,目标是总花费最小。 计价规则: - 起步4公里10元(不足4公里按4公里计,即至少10元) - 接下来的4公里(4~8公里部分),每公里2元,即这段每增加1公里加2元 - 8公里以上部分,每公里2.4元 注意:最后不足1公里按1公里计,但题目中计价规则是按每公里计费,且分段计费,所以我们可以将整个行程分成任意多段(每段独立按上述规则计费),然后求总花费的最小值。 分析: 由于分段是任意的,我们可以将行程分成1段、2段、...、n段。但显然,分太多段并不会更优(因为起步价较高,分段过多会导致每段都要收起步价)。实际上,我们只需要考虑将行程分成1段、2段或3段,因为分成更多段可能会因为起步价而增加费用。但通过分析,我们可能会发现最优解在分成1段、2段或3段中。然而,题目中给出16公里分成2段是最优的(每段8公里,花费18元,共36元;而一次走完要37.2元)。因此,我们需要考虑分段的情况。 但是,注意题目说“乘客可以根据行程公里数合理安排坐车方式”,意味着可以分成任意多段,但每段都是独立的,按计价规则计费。 然而,我们观察到: - 如果全程不超过4公里,那么花费就是10元。 - 如果全程超过4公里,我们可以考虑分成两段或多段。 但是,如何找到最小花费?我们需要动态规划?但n最大为10000000,所以不能使用O(n^2)的动态规划。 重新思考:我们是否能够推导出数学公式? 观察:对于任意一段路程x(x>0),其费用f(x)为: f(x) = 10, 当 x <= 4 10 + 2*(x-4), 当 4 < x <= 8 10 + 2*4 + 2.4*(x-8) = 18 + 2.4*(x-8), 当 x>8 注意:题目规定最后不足1公里按1公里计,但这里我们的x是整数,所以不需要特别处理(因为题目输入是整数公里数)。 现在,如果我们将整个行程n分成k段,每段的长度为x_i(非负整数,且x_1+x_2+...+x_k = n),则总费用为 sum(f(x_i))。 我们的目标是 min{ sum(f(x_i)) },其中分段数k任意,且每段长度为正整数。 但是,由于f(x)是下凸函数(可以证明,因为其导数递增:0~4公里:每增加1公里平均费用从2.5降到2.5,但实际上是分段线性的,且斜率分别为0(实际上在0~4公里内增加公里数并不增加费用,因为起步价包含4公里),然后斜率2,然后斜率2.4),所以整体是下凸的。因此,多个下凸函数的的最小值问题,通常可以通过分析得到最优解的分段长度具有某种规律。 实际上,我们可以猜测:最优解中,每一段的长度要么是某个常数,要么不会超过一个阈值(比如12公里以上?)。因为如果某一段太长,那么它每公里的平均费用会下降(超过8公里后每公里2.4元,而起步价10元在长距离中被摊薄)。但是,如果分成两段,那么每段可以避免过长的距离带来的高额费用(比如超过8公里后每公里2.4元,而如果分成两段,每段在8公里以内,那么每公里费用可能更低?)。 然而,我们注意到,在8公里以内,费用增长是线性的,且超过8公里后,每公里2.4元,这个费用比8公里以内(4~8公里部分每公里2元)要高。因此,如果我们将一段超过8公里的路程拆分成两段,其中一段恰好为8公里,另一段为剩余公里数,那么费用可能会减少吗? 实际上,我们考虑将一段x>8的行程拆成两段:ab(a+b=x),那么费用变化为: f(x) = 18 + 2.4*(x-8) f(a)+f(b) = f(a) + f(x-a) 我们需要比较f(x)f(a)+f(x-a)的大小。如果存在某个a使得f(a)+f(x-a) < f(x),那么我们就应该拆分。 因此,问题转化为:对于给定的n,我们如何选择分段数以及每段的长度,使得总费用最小? 由于n很大,我们需要一个O(1)或者O(log n)的解法。我们可以先预处理出每个长度x的费用f(x),然后考虑使用动态规划,但n最大10000000,动态规划需要O(n)的状态,但状态转移需要枚举所有可能的分段点,这样是O(n^2)的,不可行。 另一种思路:通过数学分析,我们可能发现最优解的分段长度只可能是某个特定值。实际上,我们注意到: - 在0~4公里:费用恒为10,所以每公里平均费用随距离增加而递减(从2.5降到2.5?实际上,1公里:10元/公里=10,2公里:5元/公里,3公里:10/3≈3.33,4公里:2.5) - 4~8公里:每增加1公里,费用增加2元,所以每公里平均费用:4公里:2.5,5公里:12/5=2.4,6公里:14/6≈2.333,7公里:16/7≈2.286,8公里:18/8=2.25 - 8公里以上:每公里2.4元,所以平均费用会逐渐上升(因为前8公里平均费用2.25,之后每公里2.4,所以总平均费用会从2.25逐渐上升到接近2.4) 因此,在8公里处平均费用最低(2.25元/公里),超过8公里后,平均费用会逐渐增加(因为新增的公里数费用2.4元/公里高于2.25)。所以,如果我们把行程分成每段8公里,那么平均每公里2.25元,这比单独一段超过8公里的平均费用要低(因为超过8公里后,总平均费用会高于2.25,直到超过一定距离后平均费用才可能再次下降?但不会低于2.4,所以不会低于2.25)。 但是,如果分成8公里一段,那么每段费用为18元。那么对于任意n,我们可以将其分成若干段,其中尽可能多的8公里段,然后剩下的部分(0到15公里)单独作为一段?注意,剩下的部分如果超过8公里,我们还可以再分段吗?实际上,我们允许任意分段,所以剩下的部分(小于16公里)我们也可以再分成两段,比如8公里剩下的。 然而,我们还需要考虑:剩下的部分(小于16公里)如何分段才能最小化费用? 所以,我们考虑:将n分成k段,其中k-1段都是8公里,最后一段为r = n - 8*(k-1)公里(0<r<=15,因为如果r>15,那么我们可以再分出一个8公里段)。那么总费用为18*(k-1) + f(r)。 但是,是否可能不分成8公里一段?比如分成7公里?7公里的费用:f(7)=10+2*(7-4)=16,平均约2.2857元/公里,比8公里的2.25高。所以分成8公里比7公里更优。同样,9公里:f(9)=18+2.4=20.4,平均20.4/9≈2.2667,比8公里的2.25高。所以,从平均费用的角度看,8公里是最优的。 但是,我们还需要考虑剩下的部分r。如果r=0,那么全部是8公里段,每段18元。如果r>0,那么最后一段为r公里,费用为f(r)。但是,我们也可以将最后一段前面的8公里合并?比如,最后一段为r+8公里,那么费用为f(r+8),而原来两段是18+f(r)。所以我们需要比较f(r+8)18+f(r)的大小。 我们计算一下: 如果r<=4,那么: f(r)=10 f(r+8)=18+2.4*(r) [因为r+8>8,所以用第三段计费] 比较:18+2.4*r 18+10=28 显然,当r<=4时,2.4*r<=9.6,所以18+2.4*r<=27.6<28,所以合并更优?不对,因为合并后我们少分了一段,所以原来有k-1个8公里段一段r,现在变成k-2个8公里段一段r+8。那么总费用变化:原来是18*(k-1)+10,现在是18*(k-2)+f(r+8)=18*(k-2)+18+2.4*r=18*(k-1)+2.4*r。 而原来的费用是18*(k-1)+10,所以合并后费用变化:18*(k-1)+2.4*r - [18*(k-1)+10] = 2.4*r - 10。 当r<=4时,2.4*r<=9.6<10,所以合并后费用减少(因为2.4*r-10为负,所以总费用减少了?不对,这里我们比较的是合并后的总费用与合并前的总费用,合并后的总费用是18*(k-1)+2.4*r,合并前是18*(k-1)+10,所以合并后费用减少10-2.4*r>0(因为2.4*r<10),所以合并后费用更少?不对,应该是合并后总费用=18*(k-1)+2.4*r,合并前=18*(k-1)+10,所以合并后费用更少?但这样我们就应该合并。 但是,合并后,我们少分了一段,那么合并后的那一段是r+8,如果r+8>8,那么费用计算正确。但是,合并后,我们可能还可以继续合并?所以,我们不应该保留8公里段,而是尽可能合并成更长的段?这似乎与之前认为8公里最优矛盾。 重新分析:我们之前认为8公里是最优的,是因为8公里平均费用最低(2.25元/公里)。但是,如果合并8公里一个小于4公里的段(比如r=1)成9公里,那么9公里的费用=18+2.4*1=20.4,平均2.2667元/公里,高于8公里的2.25,所以不应该合并?但是,按照上面的计算,合并后总费用减少(因为2.4*1=2.4,而原来分开的费用是18+10=28,合并后是20.4,所以总费用减少7.6元)。所以,这个比较是:一段8公里一段1公里的总费用28元,而合并成9公里只要20.4元,所以合并更优。 因此,我们之前的想法(尽可能分成8公里一段)可能不是最优的。因为合并8公里一个小段后,可能总费用更低。 所以,我们需要重新考虑:最优解中,每段的长度可能是多少?通过分析,我们可能发现,最优解中,每段的长度要么在1~8之间,要么大于等于某个值(比如8公里以上,但具体多少?)而且,我们注意到,在大于8公里后,每增加1公里费用2.4元,这个费用比4~8公里之间的2元高,所以如果可能,我们更希望将大于8公里的部分拆成4~8公里另一段(比如4公里以上)。但是,拆成两段后,新增加的一段会多出一个起步价10元,所以需要权衡。 因此,我们需要比较:将一段x>8的行程拆成两段(ab,a,b>0且a+b=x)是否比不拆更省钱?即f(x) > f(a)+f(b) 是否成立? 设g(x)=f(x)表示不分段走x公里的费用,而分段后费用为f(a)+f(b)。那么,我们要求min{g(x), min_{a=1}^{x-1} [f(a)+f(x-a)]}。 由于f(x)是分段函数,我们可以通过分析得到,当x在什么范围内时,分段比不分段更优。 我们计算一下: 当x<=8时,显然不分段(因为分段会增加起步价,所以不会更优,除了x=0,但x>=1)。 当x>8时,我们考虑如何分段。因为分段后,两段都至少4公里?不一定,比如一段1公里,另一段x-1公里(但这样可能不优,因为1公里就要10元)。 我们考虑拆成两段,每段都大于4公里?因为如果一段小于等于4公里,那么费用就是10元,而另一段是x-4公里(大于4公里),那么总费用为10+f(x-4)。那么我们需要比较f(x)10+f(x-4)的大小。 计算: 当x>8时,f(x)=18+2.4*(x-8)=2.4x-1.2 10+f(x-4)=10+ [18+2.4*((x-4)-8)] = 10+18+2.4*(x-12)=2.4x-28.8+28=2.4x-0.8? 不对,当x-4>8时(即x>12),f(x-4)=18+2.4*(x-4-8)=18+2.4*(x-12)=2.4x-28.8+18=2.4x-10.8 所以,10+f(x-4)=10+2.4x-10.8=2.4x-0.8 比较f(x)=2.4x-1.2 10+f(x-4)=2.4x-0.8:显然2.4x-1.2 < 2.4x-0.8,所以不拆更优。 那么拆成两段都大于4公里?不一定,我们也可以拆成两段,其中一段在4~8公里,另一段在大于8公里?或者两段都在4~8公里?但是,如果x>8,拆成两段都在4~8公里(即4<=a<=8, 4<=b<=8),那么需要x在8~16公里之间。那么费用为:f(a)+f(b)= [10+2*(a-4)] + [10+2*(b-4)] = 2*(a+b) + 4 = 2x+4 而如果不分段,f(x)=18+2.4*(x-8)=2.4x-1.2 比较2x+42.4x-1.2:令2x+4 < 2.4x-1.2 => 4+1.2 < 0.4x => 5.2 < 0.4x => x>13 所以,当x>13时,拆成两段4~8公里的费用2x+4会小于2.4x-1.2?不对,因为当x>13时,2x+4 < 2.4x-1.2 成立,那么拆成两段4~8公里更优?但是,x>13时,我们无法拆成两段都在4~8公里(因为4~8公里,两段最大为16,所以x<=16)。所以,当13<x<=16时,拆成两段4~8公里的费用2x+4小于2.4x-1.2,所以更优。 例如,x=14: 不分段:f(14)=18+2.4*(14-8)=18+14.4=32.4 分两段:7+7:f(7)=10+2*3=16,两段32;或者8+6:f(8)=18, f(6)=10+2*2=14,共32。所以确实32<32.4。 那么,当x>16时,我们怎么分段?我们可以拆成一段8公里一段x-8公里(x-8>8,所以x>16),那么费用为18+f(x-8)=18+ [18+2.4*(x-8-8)] = 36+2.4*(x-16) 而如果不分段,f(x)=2.4x-1.2 比较:36+2.4x-38.4=2.4x-2.4 2.4x-1.2,显然2.4x-2.4 < 2.4x-1.2,所以拆成两段(8公里x-8公里)更优。 那么,如果拆成三段呢?比如,x=17,拆成8+8+1:费用=18+18+10=46;而不分段:f(17)=2.4*17-1.2=40.8-1.2=39.6;或者拆成8+9:18+f(9)=18+20.4=38.4;或者拆成7+10:f(7)=16, f(10)=18+2.4*2=18+4.8=22.8,共38.8。所以拆成8+9(38.4)比拆成8+8+1(46)好。但38.4>39.6?不对,39.6<38.4,所以不分段更优?但之前我们推导出拆成8公里9公里(即17公里拆成89)费用38.4,而整体走39.6,所以38.4<39.6,所以拆成两段更优。 重新计算:整体走17公里:f(17)=18+2.4*(17-8)=18+2.4*9=18+21.6=39.6 拆成89:f(8)=18, f(9)=18+2.4*1=20.4,共38.4,所以38.4<39.6,所以拆成两段更优。 那么,我们是否可以认为:对于任意x>16,拆成8公里x-8公里(x-8>8)更优?那么费用为18+f(x-8)。而x-8可能还大于16,所以我们可以继续拆?所以,最终会拆成若干个8公里一个小于等于16公里的段。 因此,我们得到策略: - 如果n<=8,那么不分段,费用为f(n) - 如果n>8,那么我们可以考虑将n拆成k个8公里一个余数r(r=n%8),但注意,r可以是0~7,但这样可能不是最优,因为余数部分可能其他8公里合并?或者,我们也可以让最后一段不是r,而是r+8(然后k-1),这样最后一段就是r+8,但r+8可能大于8,所以需要计算。 实际上,我们之前发现,当13<x<=16时,拆成两段4~8公里(即每段在4~8公里)更优。所以,对于余数部分,我们并不需要限制在0~7,而是可以取0~15(因为两个8公里段可以合并成一段16公里,但16公里怎么处理?我们计算一下16公里分两段8公里:18+18=36;不分段:f(16)=18+2.4*8=18+19.2=37.2;所以分段更优(36<37.2))。但是,16公里也可以拆成两个8公里,费用36。 所以,我们考虑:将n拆成若干段,每段长度在4~8公里(包括8公里)?但是,如果一段长度超过8公里,我们也可以接受,只要总费用最小。但是,我们之前发现,超过8公里的段,如果长度在9~16公里,可能拆成两个4~8公里的段更优(比如14公里,拆成7+7,费用32;而如果拆成8+6,费用18+14=32;或者不拆:39.6?不对,14公里不拆:f(14)=18+2.4*6=32.4,所以32<32.4)。所以,对于长度在9~16公里的段,我们可能拆成两段4~8公里更优。 因此,我们规定:最后一段的长度r(在0~15公里)中,我们单独处理,而前面的部分全部是8公里段(即k个8公里,k=0,1,2,...,然后最后一段r在1~15公里)。但是,如果r=0,那么就没有最后一段。 但是,我们也可以不分成8公里一段,而是分成7公里或6公里?比如,如果n=15,那么我们可以分成5+5+5:费用=3*f(5)=3*12=36;或者分成8+7:18+16=34;或者分成15:f(15)=18+2.4*7=18+16.8=34.8;或者分成6+9:f(6)=14, f(9)=20.4,共34.4。所以,34<34.4<34.8<36,所以分成8+7最优(34元)。 所以,最后一段的长度不一定在1~7,也可以在4~8公里(这里7公里)?那么我们如何确定最后一段的长度? 因此,我们考虑:对于剩余部分r(0<=r<8),我们并不一定要单独作为一段,我们可以将前面的一个8公里段r合并成8+r公里(r>0),然后比较合并后总费用合并前总费用。合并前的费用:18+f(r),合并后的费用:f(8+r)。所以,当f(8+r) < 18+f(r)时,我们就合并。 计算f(8+r)18+f(r)(r>0): r<=4时:f(r)=10,f(8+r)=18+2.4*r,所以18+2.4*r 18+10=28 -> 18+2.4*r < 28 当 r<10/2.4≈4.166,所以当r<=4时,合并更优。 r>4且r<=8时:f(r)=10+2*(r-4)=2r+2,f(8+r)=18+2.4*r 比较:18+2.4*r 18+2r+2=2r+20 18+2.4*r - (2r+20) = 0.4r -2 当0.4r-2<0,即r<5时,合并更优;当r>5时,分开更优;当r=5时,相等。 验证:r=5: 合并:18+2.4*5=30;分开:18+f(5)=18+12=30,相等。 r=6: 合并:18+14.4=32.4;分开:18+f(6)=18+14=32,分开更优。 r=7: 合并:18+16.8=34.8;分开:18+f(7)=18+16=34,分开更优。 所以,当r<=5时,合并更优(或相等);当r>5时,分开更优。 因此,我们的策略是: - 当n<=8时,直接计算f(n) - 当n>8时,设k = n / 8(整数除法,商),r = n % 8 如果r==0,那么总费用为k*18 否则,考虑两种情况: 情况1:不合并最后一段:即k个8公里段一段r公里,费用为k*18 + f(r) 情况2:将最后一段r前面的一个8公里段合并成8+r公里,那么总段数变成k-1个8公里段一段8+r公里,费用为(k-1)*18 + f(8+r) 然后取两种情况的最小值。 但是,根据上面的分析,当r<=5时,合并更优(即f(8+r)<=18+f(r)),所以我们应该合并;当r>5时,分开更优。所以,我们可以直接: if r==0: cost = k*18 else if r<=5: cost = (k-1)*18 + f(8+r) [注意:这里k至少为1] else: cost = k*18 + f(r) 但是,当k=0时(即n<=8),我们直接计算。当n>8且k=1,r>0时,如果r<=5,那么我们需要合并,此时k-1=0,所以费用为f(8+r);如果r>5,那么费用为18+f(r)。 验证样例: n=16: k=2, r=0 -> cost=2*18=36 -> 正确 n=9: k=1, r=1 -> r<=5,所以合并:费用=f(9)=18+2.4=20.4 -> 但题目输入9时输出20.4,正确。 但是,我们的样例输入9输出20.4,而题目样例输出20.4,所以正确。 n=3: k=0, r=3 -> 直接计算f(3)=10 -> 正确 n=10: k=1, r=2 -> r<=5,合并:f(10)=18+2.4*2=18+4.8=22.8 不合并:18+f(2)=18+10=28,所以22.8更小,正确。 但是,我们再看n=14: 按我们的方法:k=1, r=6 -> r>5,所以不合并:费用=18+f(6)=18+14=32 而合并:k-1=0,费用=f(14)=18+2.4*6=32.4,所以32<32.4,所以选择不合并,正确(因为14公里拆成8+6更优,费用32)。 n=15: k=1, r=7 -> r>5,所以费用=18+f(7)=18+16=34 合并:f(15)=18+2.4*7=34.8,所以34<34.8,所以选择不合并,正确。 n=13: k=1, r=5 -> r<=5,所以合并:费用=f(13)=18+2.4*5=30 不合并:18+f(5)=18+12=30,所以相等,都可以。 n=12: k=1, r=4 -> r<=5,合并:f(12)=18+2.4*4=27.6 不合并:18+f(4)=18+10=28,所以27.6<28,合并更优。 但是,我们考虑另一种分法:12公里可以拆成两个6公里:f(6)+f(6)=14+14=28,比27.6高?所以27.6更优。所以合并成12公里一段,费用27.6。 但是,12公里也可以拆成三个4公里:3*10=30,更差。 所以,我们的策略对以上样例都正确。 但是,我们只考虑了分一段分两段(即合并与不合并),没有考虑分更多段?比如,n=12,我们也可以拆成三段:4+4+4,费用30;或者拆成5+7:f(5)=12, f(7)=16,共28。但是,我们合并成12公里一段,费用27.6,是最优的。 那么,有没有可能分成三段比我们合并后的费用27.6还低?比如6+6:28>27.6,所以不会。其他分法:3+3+6:10+10+14=34>27.6。所以27.6是最优。 因此,我们只需要考虑两种情况:要么最后一段不合并(即单独一段r),要么最后一段前面的一个8公里合并(即一段8+r公里),而前面的部分都是8公里一段。 所以,算法如下: while 输入n,当n=0结束 if n<=8: cost = f(n) else: k = n / 8; // 整数除法,商 r = n % 8; if r==0: cost = k * 18.0; else: double cost1 = k * 18.0 + f(r); // 不合并 double cost2 = (k-1) * 18.0 + f(8+r); // 合并 cost = (cost1 < cost2) ? cost1 : cost2; // 注意:当k=0时,n<=8,所以这里k>=1 // 输出cost,注意保留一位小数(如果需要) 但是,我们还需要实现f(x)函数(x>0,且x<=15,因为合并时8+r<=15,不合并时r<=7): double f(int x) { if (x<=4) return 10.0; else if (x<=8) return 10.0 + 2.0*(x-4); else // x>8 && x<=15 return 18.0 + 2.4*(x-8); } 注意:题目要求,如果结果需要保留一位小数则保留,否则不保留?但样例输出有整数有小数。我们统一用%.1f输出会保留一位小数,但整数会输出10.0,而样例输出是整数形式(如10,而不是10.0)。所以,我们需要判断:如果cost是整数,则输出整数形式,否则保留一位小数。 但是,题目要求:如果需要的话,保留一位小数。所以,我们可以先判断cost是否为整数,然后输出。 在C语言中,我们可以这样判断:如果cost==(int)cost,则输出整数形式,否则保留一位小数。 具体输出: if (cost == (int)cost) printf("%d\n", (int)cost); else printf("%.1f\n", cost); 但是,由于浮点数精度问题,我们最好避免直接比较。因为我们的费用计算都是整数的倍数,所以我们可以用整数计算,避免浮点数。 但是,f(x)在x>8时,有小数部分(乘以2.4,2.4=12/5,所以是0.2的倍数),所以总费用可能是小数(0.2的倍数)。所以,我们可以用整数计算,然后最后除以5来避免浮点精度。 但是,为了简单,我们可以用double,然后输出时判断小数部分是否为0。 或者,我们可以这样:将费用乘以10,然后取整,再判断最后一位是否为0。 但是,题目要求输出格式如样例,所以我们可以这样输出: char buffer[20]; double intpart; if (modf(cost, &intpart) == 0.0) printf("%.0f\n", cost); else printf("%.1f\n", cost); 但是,浮点数精度问题,modf可能不准确。我们可以用四舍五入到小数点后一位,然后判断小数部分是否接近0。 更简单的方法:用整数计算。我们全程使用分(单位:分)来计算,最后再转换成元。 但是,题目中费用是元,有小数。所以,我们可以用分(乘以10)来避免小数。 定义函数f(x)返回的是分(整数)?但是题目有小数,所以我们可以用double,然后输出时格式化。 考虑到浮点数精度问题,由于我们的计算都是线性的,且数字不大(n<10000000,最多1000万公里,费用最大为2.4*10000000=2400万元,double可以精确表示整数一位小数),所以我们可以用double。 但是,为了保险,我们使用整数分(即用整数表示,单位为角,1元=10角,这样就没有小数了,最后输出时再转换)。 但是,题目要求输出元,保留一位小数,所以用角计算,最后除以10,然后输出时判断整数还是小数。 但是,这样更复杂。我们直接使用double,然后输出时四舍五入到小数点后一位,并判断是否为整数。 具体输出:我们先用%.1f四舍五入到一位小数,然后检查这个字符串,如果小数部分为0,则去掉小数部分。 但是,C语言中,我们可以先输出到字符串,然后去掉后缀的0。 例如: char output[50]; sprintf(output, "%.1f", cost); // 如果output有小数部分0,则去掉 int len = strlen(output); if (output[len-1]=='0' && output[len-2]=='.') { output[len-2] = '\0'; // 去掉小数点及后面的0 } printf("%s\n", output); 但是,题目要求,如果需要的话保留一位小数。所以,我们也可以这样:先判断cost是否为整数,然后选择格式。 在C语言中,我们可以这样: if (fabs(cost - (int)(cost+0.5)) < 1e-5) // 判断是否为整数 printf("%d\n", (int)(cost+0.5)); else printf("%.1f\n", cost); 但注意浮点精度,我们使用一个很小的误差范围。 但是,由于我们的计算都是整数0.2的倍数(因为2.4元=24角,所以费用是0.4元的倍数?不对,2.4元每公里,那么每公里24角,但还有起步价,所以总费用是角的整数倍,所以最小单位是角,即0.1元)。所以,总费用一定是0.1元的整数倍。所以,小数部分只能是0或0.1,0.2,...,0.9。 因此,我们可以这样: int cents = (int)(cost * 10 + 0.5); // 四舍五入到分,然后乘以10(即单位是角) if (cents % 10 == 0) printf("%d\n", cents/10); else printf("%.1f\n", cost); // 或者用cents/10.0 但是,这样我们可能因为浮点精度问题,在乘以10时出现误差。所以,我们最好用整数计算。 我们重新设计:用整数计算费用(单位:角)。 定义f(x)函数,返回x公里的费用(单位:角): x<=4: 100角 4<x<=8: 100 + 20*(x-4) [因为2元/公里=20角/公里] x>8: 180 + 24*(x-8) [因为18元=180角,2.4元/公里=24角/公里] 然后,主函数中: if n<=8: cost = f(n) else: k = n/8 r = n%8 if r==0: cost = k * 180; else: cost1 = k * 180 + f(r); cost2 = (k-1)*180 + f(8+r); cost = min(cost1, cost2); 最后,输出时,将cost(单位:角)转换成元:整数部分为cost/10,小数部分为cost%10。 if cost % 10 == 0: printf("%d\n", cost/10); else: printf("%d.%d\n", cost/10, cost%10); 但是,注意cost%10可能是负数?所以用无符号整数?但费用非负。 或者,我们直接用整数除法取模: int yuan = cost / 10; int jiao = cost % 10; if (jiao == 0) printf("%d\n", yuan); else printf("%d.%d\n", yuan, jiao); 但是,这样输出,如果jiao=0,则输出整数;否则输出整数部分小数部分(一位)。 但是,注意:我们计算cost时,是整数(角),所以这样输出是安全的。 因此,我们采用整数计算(单位:角)来避免浮点数。 代码: #include <stdio.h> int f(int x) { if (x <= 4) return 100; else if (x <= 8) return 100 + 20*(x-4); else return 180 + 24*(x-8); // x>8 } int main() { int n; while (scanf("%d", &n) != EOF) { if (n == 0) break; int cost_cent; // 单位:角 if (n <= 8) { cost_cent = f(n); } else { int k = n / 8; int r = n % 8; if (r == 0) { cost_cent = k * 180; } else { int cost1 = k * 180 + f(r); int cost2 = (k-1) * 180 + f(8 + r); cost_cent = (cost1 < cost2) ? cost1 : cost2; } } // 输出:将角转换成元 int yuan = cost_cent / 10; int jiao = cost_cent % 10; if (jiao == 0) { printf("%d\n", yuan); } else { printf("%d.%d\n", yuan, jiao); } } return 0; } 测试样例: n=3: f(3)=100角 -> 10元 -> 输出10 n=9: k=1, r=1 -> 合并:f(9)=180+24*1=204角 -> 20.4元 -> 输出20.4 不合并:18*10 + f(1)=180+100=280角 -> 28元,所以取204角,输出20.4 n=16: k=2, r=0 -> 360角 -> 36元 -> 输出36 n=2: f(2)=100角 -> 10元 -> 输出10 但是,我们再看n=4: f(4)=100角 -> 10元 -> 输出10,正确。 n=5: f(5)=100+20*1=120角 -> 12元 -> 输出12,但题目没有要求输出12,但我们的程序应该输出12(整数形式)。 但是,题目样例输出只有3,9,16。所以,我们还需要测试其他样例。 注意:题目要求输入多组,直到0结束。 我们编写代码,并测试。 但是,注意:当k=0时,n<=8,所以不会进入else分支。当n>8时,k>=1,所以当r>0时,k-1>=0(因为k>=1),所以不会出现负数。 但是,当n=8时,k=1, r=0,所以cost_cent=180角=18元,但f(8)=100+20*4=180角,所以正确。 n=7: f(7)=100+20*3=160角=16元,正确。 n=10: k=1, r=2 -> 合并:f(10)=180+24*2=228角=22.8元 不合并:180 + f(2)=180+100=280角=28元,所以取228角,输出22.8 n=14: k=1, r=6 -> 不合并:180+f(6)=180+ (100+20*2)=180+140=320角=32元 合并:f(14)=180+24*6=180+144=324角=32.4元,所以取320角,输出32 n=15: k=1, r=7 -> 不合并:180+f(7)=180+160=340角=34元 合并:f(15)=180+24*7=180+168=348角=34.8元,所以取340角,输出34 n=12: k=1, r=4 -> 合并:f(12)=180+24*4=276角=27.6元 不合并:180+f(4)=180+100=280角=28元,所以取276角,输出27.6 但是,题目没有给出12的样例,所以我们可以自己验证。 所以,代码实现如下。 注意:整数除法,n/8是整除,n%8是余数。 但是,n<10000000,所以整数不会溢出(最大费用:k最大为n/8,最大为1250000,然后乘以180,最大为225000000角,在int范围内(int通常为2^31,约21亿))。 因此,我们使用int类型。 但是,为了确保,我们使用long也可以,但题目n<10000000,所以最大费用大约225000000,在int范围内(2^31-1=2147483647),所以用int即可。 代码:</think>### 解决方案:计算出租车最小花费 #### 问题分析 出租车计价规则: - 起步4公里10元(不足4公里按4公里计费) - 4~8公里:每公里2元 - 超过8公里:每公里2.4元 - 行程最后不足1公里按1公里计费 乘客可以将整个行程分成多段,每段独立计费,目标是使总费用最小。通过数学分析发现: - 当行程距离 ≤ 8公里时,直接计费最优 - 当行程距离 > 8公里时,最优策略是: - 尽可能多地分成8公里段(每段18元) - 剩余部分根据长度选择独立计费或与最后一段8公里合并计费 #### 算法设计 1. **定义计费函数**:计算单段行程的费用(单位:角) 2. **处理输入**:多组测试数据,直到输入0结束 3. **核心逻辑**: - 若距离 ≤ 8公里:直接计算费用 - 若距离 > 8公里: - 计算完整8公里段数量(k)剩余距离(r) - 若r=0:总费用 = k×180角 - 若r>0:比较两种方案: 1. 独立计费:k个8公里段 + 剩余r公里费用 2. 合并计费:(k-1)个8公里段 + (8+r)公里费用 4. **输出**:将角转换为元,整数输出或保留一位小数 #### C语言实现 ```c #include <stdio.h> // 计算单段行程费用(单位:角) int calculate_fare(int distance) { if (distance <= 4) { return 100; // 10元 } else if (distance <= 8) { return 100 + 20 * (distance - 4); // 起步10元 + 每公里2元 } else { return 180 + 24 * (distance - 8); // 起步18元 + 每公里2.4元 } } int main() { int n; while (scanf("%d", &n) != EOF) { if (n == 0) break; // 输入0结束 int total_cents; // 总费用(单位:角) if (n <= 8) { total_cents = calculate_fare(n); } else { int k = n / 8; // 完整8公里段数量 int r = n % 8; // 剩余公里数 if (r == 0) { total_cents = k * 180; // 全为8公里段 } else { // 方案1:独立计费(k个8公里 + 剩余r公里) int option1 = k * 180 + calculate_fare(r); // 方案2:合并计费((k-1)个8公里 + 1个(8+r)公里) int option2 = (k - 1) * 180 + calculate_fare(8 + r); // 选择费用更低的方案 total_cents = (option1 < option2) ? option1 : option2; } } // 转换为元输出(整数或保留一位小数) int yuan = total_cents / 10; int jiao = total_cents % 10; if (jiao == 0) { printf("%d\n", yuan); // 整数输出 } else { printf("%d.%d\n", yuan, jiao); // 保留一位小数 } } return 0} ``` #### 代码解释 1. **计费函数** `calculate_fare`: - 使用角(1元=10角)作为单位避免浮点误差 - 分三段计算:≤4公里、4~8公里、>8公里 2. **输入处理**: - `while (scanf("%d", &n) != EOF)` 循环读取多组输入 - `n == 0` 时退出循环 3. **核心逻辑**: - `k = n / 8`:完整8公里段数量 - `r = n % 8`:剩余公里数 - 当 `r > 0` 时,比较两种方案: - 独立计费:保持剩余部分独立 - 合并计费:将剩余部分与一个8公里段合并 4. **输出处理**: - 将角转换为元(`yuan = total_cents / 10`) - 判断角部分是否为0,决定输出整数或小数 #### 样例验证 | 输入 | 计算过程 | 输出 | |------|----------|------| | 3 | 直接计费:10元 | 10 | | 9 | 合并方案:f(9)=20.4元 | 20.4 | | 16 | 2个8公里段:18×2=36元 | 36 | | 12 | 合并方案:f(12)=27.6元 | 27.6(输出27.6) | | 14 | 独立方案:18+f(6)=32元 | 32 | | 15 | 独立方案:18+f(7)=34元 | 34 | #### 复杂度分析 - 时间复杂度:O(1) 每组数据 - 空间复杂度:O(1) 仅用常数空间 --- ### 相关问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值