题目:River Hopscotch POJ - 3258
每年奶牛们都要举办各种特殊版本的跳房子比赛,包括在河里从一块岩石跳到另一块岩石。这项激动人心的活动在一条长长的笔直河道中进行,在起点和距离起点 L 远的终点各有一块岩石 (1 ≤ L ≤ 10^9)。在起点和终点之间,有 N 块岩石 (0 ≤ N ≤ 50000),每块岩石与起点的距离分别为 Di (0 < Di < L)。
在比赛过程中,奶牛轮流从起点出发,尝试到达终点,每一步只能从一块岩石跳到另一块岩石。当然,实力不济的奶牛无法抵达终点,在河中间就退出比赛了。
农夫约翰为他的奶牛们感到自豪并且年年都观看了这项比赛。但随着时间的推移,看着其他农夫的胆小奶牛们在相距很近的岩石之间缓慢前行,他感到非常厌烦。他计划移走一些岩石,使得从起点到终点的过程中,最短的跳跃距离最长。他可以移走除起点和终点外的至多 M 块岩石 (0 ≤ M ≤ N)。
请帮助农夫约翰确定:移走这些岩石后,最短跳跃距离的最大值是多少?
Input:
第 1 行包含以单个空格分隔的三个整数 L, N, M。
第 2 到 N + 1 行,每行一个整数,表示每个岩石与起点的距离。不会有两个岩石出现在同一个位置。
Output:
输出一个整数,即最短跳跃距离的最大值。
Simple Input:
25 5 2 2 14 11 21 17
Simple Output:
4
分析:
这是一个最大化最小值的问题,设定条件C(x):任意两块剩余岩石间距不小于x,其中“任意”保证了最小值,“不小于”保证了所有小于xmax的距离都满足C(x),所有大于xmax的距离都不满足C(x),可以使用二分搜索来找答案;
对于每一个尝试答案,要判断是否满足C(x),这里采用贪心的方法,先把所有的岩石(包括起点和终点)都排序,之后从起点开始选,保证选的每一块岩石间距>=x,如果可以选够N-M+2块,则x满足条件,如果已经选到了最后一块即终点还没有选够岩石块数,则x不满足条件;
至于mid的值的变换,可以这么想,起点是0,终点是大于L的任意值,设定区间[s,e)包含解,之后的操作要始终保证解的区间左闭右开,当e-s==1的时候,s就是答案,这种分析方法实际上避免了思考这个解是否真的是岩石的间距而不是某一个中值。具体mid应该替换s还是e,可以画一个数轴,其中xmax就是所要求的解,这个点之前的部分都满足条件,之后的部分都不满足条件,如果C(mid)==true,则mid<=xmax,所以找解应该在右边的区间,令s=mid,这样同时保证了区间的左边是闭的;如果C(mid)==false,则mid>xmax,同理令e=mid。
总结一句,二分搜索的广义应用,分为搜索和判断两步,搜索就是二分,注意解的区间的选取以及退出二分的条件;判断就涉及了其他的算法,根据实际问题有所不同,这道题中使用了贪心,不过还挺简单的
代码:
#include<iostream> #include<algorithm> using namespace std; typedef long long ll; ll rock[50010]; int N,M; ll L; bool C(int x){ int cur=0,num=1; for(int i=1;num<N-M+2&&i<=N+1;i++){ if(rock[i]-rock[cur]>=x){ num++;cur=i; } } if(num==N-M+2){ return true; } return false; } int main(){ cin>>L>>N>>M; rock[0]=0;rock[N+1]=L; for(int i=1;i<=N;i++){ cin>>rock[i]; } sort(rock+1,rock+N+1); ll s=0,e=L+1; while(e-s>1){ ll mid=s+(e-s)/2; if(C(mid)){ s=mid; } else{ e=mid; } } cout<<s; return 0; }