(题目是英文的,好烦,我直接讲一下题意了)题目连接
题意: 题目给你一个L,L表示的是这段路的长度,即[0, L],接下来一个N表示在这条路径(除两头0和L)上有N块石头,接下来给定一个M,代表可以去除M块石头,我们要做的就是求出去掉M块石头后的两两石头间距离最小值的最大值。
对于这样的题,一开始真还没想到是二分,这道题给了我关于二分用法的一个突破,一开始用了个O(n^2)的暴力算法,但显然是不行的,对于这样的题,想到二分是因为我们可以假设已经找到了这样的最后答案ans,使得对于ans,刚好有M块石头去掉后的最小间距为这些最小间距的最大值为ans。
下面先附上二分算法的代码,然后基于其给予讲解:
while(left<=right) { mid=(left+right)/2; int start=0; int sum=0; for(int i=1; i<=N; i++) { if(a[i]-start>=mid) { start=a[i]; } else sum++; } if(sum<=M) { left=mid+1; ans=mid; } else { right=mid-1; } }
我们的利用left与right分别为左端口与右端口,mid为其平均值,在for语句内,我们一一对每个石头进行操作(这样的时间复杂度为O(n*log n)),假如间距小于mid,那么这块石头就可以移走,所以可移动的石头数+1,反之,间距“>=”mid的时候,意味目前选取的起跳点到目标点的距离超过了mid,不可挪动,将起跳点移位到目标点的位置,再往后判断。接下来我们判断sum的值与left与right之间的关联,如果可移动的石头的数量“>=”M说明有扩充的空间,但为了避免超出,我们用ans保存当前mid下的值(因为其中存在“==”符号,可能有符合的情况),且让left往下走,另一方面,当“>M”时,说明过头了,将右端口往下移。
if(a[i]-start>=mid) { start=a[i]; } else sum++;
构成二分的基础:讲一下这个if语句的作用,这是这个问题的重难点,对于每一块石头,我们从0作为起跳点,往后,如果这两块石头间的距离大于等于mid,那么说明这块石头得保留下来,作为下一个起跳点,否则的话由于两两距离小于mid,如果不删去的话就与给出的最小值为mid不符合,所以,我们删除这块石头,并往后判断。
然后这也就反应出我们为什么在"sum“<=”M"而不是“<”的时候进行left=mid+1;与ans=mid,因为这个时候存在:“=”的理由:此时最小距离的值小于ans此时的值。
可能讲解的不到位,附上AC代码:
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; typedef long long ll; int a[50005]; int main() { int L,M,N; while(scanf("%d %d %d",&L, &N, &M)!=EOF) { a[0]=0; for(int i=1; i<=N; i++) { scanf("%d",&a[i]); } sort(a+1, a+N+1); N++; a[N]=L; int left,right,mid; int ans=0; left=0; right=L; while(left<=right) { mid=(left+right)/2; int start=0; int sum=0; for(int i=1; i<=N; i++) { if(a[i]-start>=mid) { start=a[i]; } else sum++; } if(sum<=M) { left=mid+1; ans=mid; } else { right=mid-1; } } printf("%d\n",ans); } return 0; }