主要想借这道题对二分答案的区间问题进行一个反思和总结
代码
#include<bits/stdc++.h>
using namespace std;
int N, C;
int point[100008];
bool check(int y)
{
int cnt=0;
int i;
int last=point[1];
for (i = 2; i <= N; i++)
{
if (point[i] - last < y)
{
cnt++;
}
else
last = point[i];
}
if (cnt <= N - C)return 1;
else return 0;
}
int main()
{
cin >> N >> C;
int i;
for (i = 1; i <= N; i++)
{
cin >> point[i];
}
sort(point+1, point+1 + N);
int l = 1, r = point[N]-point[1];
int cc = -1000;
while (l < r)
{
int mid = (l+r)/2;
if (check(mid))
{
l = mid + 1;
}
else
r = mid -1;
}
cout << l;
}
情况一:
看这块核心代码:
while (l < r)
{
int mid = (l+r)/2;
if (check(mid))
{
l = mid + 1;
}
else
r = mid -1;
}
分析一下这种情况:当check(mid)函数返回1的时候说明mid是一个可行解,但不知道是不是一个最优解。本题中要求的是最短距离的最大值。格外留意这个最大值。此时如果mid是一个符合题目调节的最短距离的值但是我们不知道在[mid,right]中还有没有比mid更大的可行解就是我们所说的最优解问题。所以说l=mid+1这种写法是错误的。mid也有可能是最优解所以我们寻找最优解的区间应该是[mid,right]而不是[mid+1,right].当check(mid)函数返回0时说明mid这个解不符合题意所以说我们寻找的区间就没有必要包括mid了而是[left,mid-1].
情况二:
作如下改正:
while (l < r)
{
int mid = (l+r)/2;
if (check(mid))
{
l = mid ;
}
else
r = mid -1;
}
按照上面所说这就是对的吗?其实还是ac不了。分析一下当left=right-1时mid=(left+right)/2=left;因为(left+right)/2就是一个下取整函数。所以说l=mid而我们结束循环的条件是left<right此时就会产生死循环,从而造成了测评结果是TLE.所以我们还要继续改正那原因出在哪呢?[left,right]我们采用的是闭区间的二分当区间二分到足够小时有可能会发生死循环。那么如果我们将闭区间换成开区间呢。[left,right),mid=(left+right)/2,换成
若mid为解left=mid+1,
mid不为解则right=mid.
结束循环的条件为right=left+1;此时left就是最优解。
开区间是要比闭区间“宽松”可以避免死循环。
while (l+1 < r)
{
int mid = (l+r)/2;
if (check(mid))
{
l = mid+1 ;
}
else
r = mid ;
}
这种写法也是正确的,但是也没那么好区分下面介绍一种方便记忆的写法。
情况三:
我们定义一个ans来储存我们的最优解就是最短距离的最大值。
若mid为可行解
则更新ans ,ans=max(ans,mid)
我们就将mid存储在了ans中下一步我们该搜索的区间就不用包含mid了应为[mid+1,right]
若mid为无效解
right=mid-1我们的搜索区间就变为了[left,mid-1]
我们将这种写法和第一种写法进行对比一下看看还会不会发生死循环的情况。当left=right-1时mid=(left+right)/2=left,赋值left=mid+1=right此时left=right.但是别玩了修改最原始循环的条件left<right为left<=right此时这个值也是要去检验的所以变成了while(left<=right)
附上代码:
int ans=-1;
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid)
{
ans=max(ans,mid);
l=mid+1;
}
else
r=mid-1;
}
cout<<ans
总结:
整数二分这一块怎么取区间还是比较麻烦的,第三种方法还是比较好的。看大家能理解哪种,适合自己的就好。