搜索
您的当前位置:首页正文

PASCAL贪心算法

来源:抵帆知识网
贪心策略

当一个问题具有最优子结构时,一般我们会想到用动态规划法去解它,但是有些问题存在着更简单有效的方法,我们只要总是做出当前看来最好的选择就可以了。贪心算法所作的选择可以依赖于以往所作过的选择,但决不依赖于将来的选择,也不依赖于子问题的解,这使得算法在编码和执行的过程中都有着一定的速度优势。如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。但是贪心算法并不是对所有的问题都能得到整体最优解或最理想的近似解,与搜索法等通法比较,它的适用区域相对狭窄许多,因此正确的分析、归纳、判断贪心法的应用是十分重要的。同时我们也应该知道贪心是一种方法,而不是算法,贪心法的应用需要结合其他算法。通常而言,贪心策略需要结合排序算法。【例题】删数问题【问题描述】键盘输入一个高精度的正整数N,去掉其中任意S个数字后剩下的数字按原左右次序将组成一个新的正整数。编程对给定的N和S,寻找一种方案使得剩下的数字组成的新数最小。输入数据均不需要判错。输出应包括所去掉的数字的位置和组成的新的正整数。(N不超过240位)【问题分析】对于这道试题,我们可以采用这样一种解题方式:因为要删除S个数字,我们可以一个一个的删,每一次删除的目的都是使剩下的数尽量小。那么在每一次删除时,我们应该选择那个数字?最大的数字还是最左的数字?例如5768,通过观察可以知道删除7这个数字可以使剩余的数最小。通过思考可以得出结论,每一次删除的数字应该是这个数字序列中降序子序列最左边的数字。具体可以这样操作,按高位到低位的方向搜索递减区间。若不存在递减区间,则删尾数符;否则删递减区间的首数符,这样形成了一个新的数串。然后回到串首,重复上述规则,删下一数符…以此类推,直至删除S个数符为止。例如:N=“8796542”,S=4删数过程如下:N=“8796542”{最左边的递减序列是87,删除“8”}“796542”{最左边的递减序列是96542,删除9}“76542”{最左边的递减序列是76542,删除“7”}“6542”{最左边的递减序列是6542,删除“6”}“542”【程序代码】programdelete;vari,j,k,s:integer;N:string;beginreadln(n);readln(s);whiles>0dobeginI:=1;while(i1and(n[1]=‘0’)dodelete(n,1,1);writeln(n);end.【问题思考】由例题可知,贪心算法的实现需要思考两个基本的问题:如何建立和方法的正确性。如何建立。怎样从一个规模较小的解推出规模较大的解呢?拿这个例题来说,从数串5768删除2位数的模拟过程中推广到240位的数串删数过程。方法的正确性。确保贪心算法确实是有效是使用贪心算法的关键所在。确定贪心算法是有效的,那么解决该问题在时间复杂度还是在空间的使用方面与其他方法想比较显得更为优势。以例题为例,删数方法是首先按高位到低位的方向搜索递减区间。若不存在递减区间,则删尾数符;否则删递减区间的首数符,这样形成了一个新的数串。这样获得的新数肯定是最小的新数。对于上述例题,假如我们按照其他的删数过程是正确的,删除最左边的数或者每次删除最大的数。同样以5768为例。删除5得768,删除8得576,都不是最小的数值,假设不成立,证明这种方式是错误的,按照原先设定的方法所得到的解是最优解。由上例可以获知,贪心的使用需要严格的证明,保证问题的严密性,贪心方法的使用还需呀结合其他算法的使用,例如:排序、模拟、搜素、枚举等等,但是一旦证明了贪心方法使用的正确性,程序的运行效率会大大提高。证明使用贪心方法的正确性依靠的是严密的数学头脑和以往的经验积累,假如不具备这些条件,使用该方法需要谨慎。二.经典例题分析1.混合牛奶(milk.pasUSACO练习题)【问题描述】牛奶包装是一个如此低利润的生意,以至于尽可能低地控制初级产品(牛奶)的价格变得十分重要。请帮助Merry的牛奶制造公司(MerryMilkMakers')以尽可能最廉价的方式取得他们所需的牛奶。Merry的牛奶制造公司从一些农民那购买牛奶,每个农民卖给牛奶制造公司的价格不一定相同。而且,如一只母牛一天只能生产一定量的牛奶,农民每一天只有一定量的牛奶可以卖。每天,Merry的牛奶制造公司从每个农民那购买一定量的牛奶,少于或等于农民所能提供的最大值。现给出Merry牛奶制造公司的每日的牛奶需求,连同每个农民的可提供的牛奶量和每加仑的价格,请计算Merry的牛奶制造公司所要付出钱的最小值。注意:每天农民生产的牛奶的总数对Merry的牛奶制造公司来说是足够的。【输入格式】第1行:二个整数,N和M。第一个数值N(0<=N<=2,000,000)是Marry的牛奶制造公司的一天需要牛奶的数量。第二个数值,M,(0<=M<=5,000)是提供牛奶的农民个数。第2到M+1行:每行二个整数,Pi和Ai。Pi(0<=Pi<=1,000)是农民i的牛奶的价格;Ai(0<=Ai<=2,000,000)是农民i一天能卖给Marry的牛奶制造公司的牛奶数量。【输出格式】单独的一行包含单独的一个整数,表示Marry的牛奶制造公司拿到所需的牛奶所要的最小费用。【样例输入】【样例输出】10056305209386401080302.门口的树(trees.pas)【问题描述】现在我们国家开展新农村建设,农村的住房建设纳入了统一规划,统一建设,政府要求每一住户门口种些树。门口路边的地区被分割成块,并被编号成1..N。每个部分为一个单位尺寸大小并最多可种一棵树。每个居民房子门前被指定了三个号码B,E,T。这三个数表示该居民想在B和E之间最少种T棵树。当然,B≤E,居民必须记住在指定区不能种多于区域地块数的树,所以T≤E-B+l。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量,尽量较少政府的支出。【输入格式】第一行包含数据N,M,区域的个数(04)拆分成从2开始的连续的自然数的和,如果最后有剩余的数,将这个剩余的数在优先考虑后面项的情况下平均分给前面的各项。证明:先来看看几个n比较小的例子,看能否从中找出规律:n5678910分解方案23243435234235最大的乘积6812152430可以发现,将n分解成a1,a2,a3,a4,…,am这m个自然数,该序列为从2开始的一串由小到大的自然数,如果a1为1,则对乘积没有影响,而且使n减少,没有实际意义,只有特殊情况如n为3、4时才可能用上。设h>=5,可以证明当将h拆分为两个不相同的部分并且两部分都大于1时两部分的乘积大于h。证明如下:将h分为两部分:a,h-a其中2<=a2*a,所以a*(h-a)-h>2*a*(a-1)-a*a=a*a-2*a=a*(a-2)又因为a>=2,所以a*(a-2)>=0,所以a*(h-a)-h>O即a*(h-a)>h。从上面的证明可以看出,对于指定的正整数,如果其大于等于5,将它拆分为不同的部分后乘积变大,对于中间结果也是如此。因此可以将指定的n,依次拆成a1+a2+a3+a4+…+am,乘积最大。现在的问题是如何拆分才能保证n=a1+a2+a3+a4+…+am呢?可以先这样取:当和不足n时,a1取2,a2取3,…,am-1取m,即从2开始按照自然数的顺序取数,最后剩余的数给am,如果am<=am-1,此时am跟前面的数字出现了重复,则把am从后面开始平均分布给前面的m-1个数。为什么要从后面开始往前呢?同样是考虑数据不出现重复的问题,如果是从前面往后面来平均分配,如2加上1以后变成3,就跟后面的已有的3出现了重复。这样操作到底是否正确、是否能保证乘积最大呢?还要加以证明。证明过程如下:2

设两个整数a,b的和为2s,且a<>b,设a=s-1,则b=s+1,a*b=(s-1)*(s+1)=s-1,2

如果a=s-2,则b=s+2,a*b=(s-2)*(s+2)=s-4。a-b的绝对值越小,乘积的常数项越大,即乘积越大,上面的序列a1,a2,a3,a4,…,am正好满足了a-b的绝对值最小。但是还要注意两个特例就是n=3和n=4的情况,它们的分解方案分别为1,2和1,3,乘积分别为2和3。以n=10为例,先拆分为:10=2+3+4+1,最后一项为1,比4小,将其分配给前面的一项,得到10=2+3+5,所以最大的乘积为2*3*5=30。以n=20为例,拆分为:20=2+3+4+5+6,正好是连续自然数的和,所以最大乘积为2*3*4*5*6=720。再以n=26为例,先拆分为:26=2+3+4+5+6+6,因为最后一项为6,不比最后第二项大,所以将其平均分给前面的项,优先考虑后面的项,即前面的4项各分到1,笫5项6分到2,最后是26=3+4+5+6+8,所以最大的乘积为3*4*5*6*8=2880。由于n可能大到10000,分解之后的各项乘积位数比较多,超过普通的数据类型的位数,所以要用到高精度运算来进行整数的乘法运算,将结果保存在数组里。5.合并果子(fruit.pasNOIP2004年提高组第2题)【问题描述】在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。【输入格式】输入文件fruit.in包括两行。第一行是一个整数n(1≤n≤10000),表示果子的种类数。第二行包含n个整数,用空格分隔,第i个整数ai(1≤ai≤20000)是第i种果子的数目。【输出格式】输出文件fruit.out包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于231。【输入样例】【输出样例】315129【数据规模】对于30%的数据,保证有n<=1000;对于50%的数据,保证有n<=5000;对于全部的数据,保证有n<=10000。【问题分析】解决方法:模拟+排序+贪心。根据样例数据可知,果子数越小的堆,应越早合并,样例模拟合并的过程就是算法实现的过程。首先对所有的果子有小到大进行排序,合并最小果子堆,重新排序,继续合并最小果子堆,重复至完成所有果子的合并,得出最终的体力消耗值。程序代码采用的就是这种方式。但是不难发现,上面的方法需要进行n-1次的排序,比较浪费时间,改变一下思维方式,用空间换取时间的方式,另设一个数组b存放每次合并后的堆。具体方式,首先读入n堆果子的重量,并按递增顺序进行排序,得到序列a[1]…a[n],并增加两个数a[n+1]=a[n+2]=maxinteger。设b序列初始值为空,然后从a序列和b序列的第一个元素开始寻找合并方案,并设定x和y分别为数组a和数组b的移动指针变量。这样,每次合并的方案不在乎以下三种方案:1合并两个初始堆(a[x]和a[x+1]),合并后指针变量移动为:x:=x+2;2一个初始堆a与另一个合并堆b进行合并(a[x]+b[y]),合并后指针变量移动为:x:=x+1;y:=y+1;3两个合并堆b进行合并(b[y]+b[y+1]),合并后的指针移动为:y:=y+2;不难得出,每次合并后最小体力消耗值为:Total:=total+min{a[x]+a[x+1],a[x]+b[y],b[x]+b[x+1]}。6.守望者的逃离(escape.pas2007年普及组第3题)【问题描述】恶魔猎手尤迫安野心勃勃,他背叛了暗夜精灵,率深藏在海底的那加企图叛变,守望者在与尤迪安的交锋中遭遇了围杀。被困在一个荒芜的大岛上。为了杀死守望者,尤迪安开始对这个荒岛施咒,这座岛很快就会沉下去,到那时岛上的所有人都会遇难:守望者的跑步速度为17m/s,以这样的速度是无法逃离荒岛的。庆幸的是守望者拥有闪烁法术,可在1s内移动60m,不过每次使用闪烁法术都会消耗魔法值10点。守望者的魔法值恢复的速度为4点/s,只有处在原地休息状态时才能恢复。现在已知守望者的魔法初值M,他所在的初始位置与岛的出口之间的距离S,岛沉没的时间T。你的任务是写一个程序帮助守望者计算如何在最短的时间内逃离荒岛,若不能逃出,则输出守望者在剩下的时间内能走的最远距离。注意:守望者跑步、闪烁或休息活动均以秒(s)为单位。且每次活动的持续时间为整数秒。距离的单位为米(m)。【输入文件】输入文件escape.in仅一行,包括空格隔开的三个非负整数M,S,T。【输出文件】输出文件escape.out包含两行:第1行为字符串“Yes”或“No”(区分大小写),即守望者是否能逃离荒岛。第2行包含一个整数,第一行为“Yes”(区分大小写)时表示守望着逃离荒岛的最短时间第一行为“No”(区分大小写)时表示守望者能走的最远距离。【样例1输入】【样例1输出】392004No197【样例2输入】【样例2输出】3625510Yes67.排座椅(seat.pasNOIP2008年普及组第2题)【问题描述】上课的时候总有一些同学和前后左右的人交头接耳,这是令小学班主任十分头疼的一件事情。不过,班主任小雪发现了一些有趣的现象,当同学们的座次确定下来之后,只有有限的D对同学上课时会交头接耳。同学们在教室中坐成了M行N列,坐在第i行第j列的同学的位置是(i,j),为了方便同学们进出,在教室中设置了K条横向的通道,L条纵向的通道。于是,聪明的小雪想到了一个办法,或许可以减少上课时学生交头接耳的问题:她打算重新摆放桌椅,改变同学们桌椅间通道的位置,因为如果一条通道隔开了两个会交头接耳的同学,那么他们就不会交头接耳了。请你帮忙给小雪编写一个程序,给出最好的通道划分方案。在该方案下,上课时交头接耳的学生对数最少。【输入格式】输入文件的第一行,有5个用空格隔开的整数,分别是M,N,K,L,D(2<=N,M<=1000,0<=K从③取3张牌放到②(9111010)->从②取1张牌放到①(10101010)。【输入文件】N(N堆纸牌,1<=N<=100)A1A2…An(N堆纸牌,每堆纸牌初始数,l<=Ai<=10000)【输出文件】所有堆均达到相等时的最少移动次数。【输入样例】498176【输出样例】35.旅行家的预算(travel.pas1999普及组第3题)【问题描述】一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的)。给定两个城市之间的距离D1、汽车油箱的容量C(以升为单位)、每升汽油能行驶的距离D2、出发点每升汽油价格P和沿途油站数N(N可以为零),油站i离出发点的距离Di、每升汽油价格Pi(i=1,2,…,N)。计算结果四舍五入至小数点后两位。如果无法到达目的地,则输出“NoSolution”。【输入格式】输入文件travel.in有若干行数据。第一行有5个以空格分隔的数,分别表示D1、C、D2、P、N。第二行至第n+1行每行有2个以空格分隔的数,分别表示N个油站的距离Di和价格Pi.【输出格式】输出文件travel.out只有一行数据,为所需的最小费用或NoSolution。【输入样例】275.611.927.42.821022.92202.2【输出样例】26.956.Car的旅行路线(car.pas2001年提高组第4题)【问题描述】又到暑假了,住在城市A的Car想和朋友一起去城市B旅游。她知道每个城市都有四个飞机场,分别位于一个矩形的四个顶点上,同一个城市中两个机场之间有一条笔直的高速铁路,第i个城市中高速铁路了的单位里程价格为Ti,任意两个不同城市的机场之间均有航线,所有航线单位里程的价格均为t。32城市3的4个机场221221121222城市2的4个机场32城市1的4个机场图例:圆点表示机场,其中半透明的圆点表示输入数据中未直接给出双线表示飞机航线单线表示高速铁路线。图中未画出所有航线和铁路线。那么Car应如何安排到城市B的路线才能尽可能的节省花费呢?她发现这并不是一个简单的问题,于是她来向你请教。任务:找出一条从城市A到B的旅游路线,出发和到达城市中的机场可以任意选取,要求总的花费最少。【输入格式】输入文件有若干行数据:第一行为一个正整数n(0<=n<=10),表示有n组测试数据。S(0

因篇幅问题不能全部显示,请点此查看更多更全内容

Top