解题思路
乍一看题目好像用贪心算法就能解决,为此还专门重新回去看了贪心部分的题目 #10006. 「一本通 1.1 练习 2」数列分段。当时解决这道题以为要根据贪心的做法,先按大小将坐标升序排列然后再考虑牛的位置,但是发现因为坐标随机分布,这样无法确定最优子结构。想要确定最优子结构,必须给定坐标之间的最小间距,通过这个距离遍历坐标,依次判定下一头牛的位置。按照最优子结构最后得出来的答案是确定最小距离的可能坐标里所最多能放置牛的数目,如果这个数目大于牛的头数则最小距离符合题意,反之不符合。题目要求“最小距离的最大值”,这里就需要用二分法逐步缩小范围,最终确定答案
1.自定义check函数,用来判断在确定的最小距离下是否能安排所有的牛
bool check(int* x, const int& n, const int& m, const int& bound)
{ //x:可选的坐标 n:坐标的数目 m:题目中需要安排的牛的总数 bound:两头牛之间的最小距离
int temp = x[0]+bound; //初始化下一头牛坐标下界,确保牛的距离大于bound
int cont = 1; //初始化已安排牛的头数:1头
for(int i = 1; i < n; i++)
{
if(temp <= x[i]) //如果两头牛距离大于bound
{
cont++; //更新已安排的牛的数目
temp = x[i]+bound; //更新坐标
}
if(cont >= m) //如果已安排的牛的数目已经大于总数,这个解一定合法
break;
}
if(cont < m)
return 0;
return 1;
}
2.利用二分法逐步缩小最小距离的取值范围
int ends = x[n-1]-x[0];
int output;
for(int bgin = x[0]; bgin <= ends;)//bgin > ends时循环结束
{
int mid = (bgin+ends)>>1; //中间值mid,同时作为check函数值参数输入验证
if(check(x, n, m, mid) == 0) //如果mid值不合法,说明大于mid-1的值均不合法
{
ends = mid-1; //右边界设置为mid-1;
}
else //如果mid值合法,小于mid+1的值均合法
bgin = mid+1;
output = ends;
}
这里要注意边界情况,首先可以知道,从左边界bgin到答案的所有解均满足check条件。考虑到有这样一种情况:(这里假设答案为28)check函数检查28合法性,发现合法,因此将bgin更新为28+1=29。此后bgin不会再发生变化flag只会检查大于等于左边界29的解,并且一定都不合法,因此将右边界逐步缩小,直到ends = mid-1 =28,ends < begin,循环退出,此时应该输出ends。
因此可以得出结论,在使用二分法逼近答案时,循环继续的条件是ends >= begin,极端情况输出从初始边界到答案的前一个值均不合法的一侧的最后边界。
完整答案
#include <iostream>
#include <algorithm>
using namespace std;
int x[100005];
bool check(int* x, const int& n, const int& m, const int& bound)
{
int cont = 1;
int temp = x[0]+bound;
for(int i = 1; i < n; i++)
{
if(temp <= x[i])
{
cont++;
temp = x[i]+bound;
}
if(cont >= m)
break;
}
if(cont < m)
return 0;
return 1;
}
int main()
{
int n, m;
cin >> n >> m;
for(int i = 0; i < n; i++)
{
cin >> x[i];
}
sort(x, x+n);
int ends = x[n-1]-x[0];
int output;
for(int bgin = x[0]; bgin <= ends;)
{
int flag = (bgin+ends)>>1;
if(check(x, n, m, flag) == 0)
{
ends = flag-1;
}
else
bgin = flag+1;
output = ends;
}
cout << output;
return 0;
}