题目描述
有一条河,河中间有一些石头,已知石头的数量和相邻两块石头之间的距离。现在可以移除一些石头,问最多移除m块石头后(首尾两块石头不可以移除),相邻两块石头之间的最小距离的最大值是多少。
输入数据
第一行输入两个数字,n(2<=n<=1000)为石头的个数,m(0<=m<=n-2)为可移除的石头数目 随后n-1个数字,表示顺序和相邻两块石头的距离d(d<=1000)
输出数据
输出最小距离的最大值
样例输入
4 1
1 2 3
样例输出
3
解题思路
- 找到一个距离D,满足:最多移除m个石头后,最近的两个石头的距离不小于D
- 定义一个check函数来判断D是否满足条件,(贪心)
check函数:
循环依次考虑相邻石头,如果距离小于d,则去掉当前石头
若所有相邻石头的距离都不小于d,则返回1,满足条件
若移除的石头数目大于m个,返回0,不满足条件(说明当前d小了)
- 通过二分查找,找到满足条件且最优的D
代码
#include <iostream>
#include <math.h>
using namespace std;
long dis[100001] = {0}, a[100001]; //a:相邻两石头间距离 dis:从头 到 当前石头的距离
long long n, m;
// check 最多移除m个石头后 任意相邻两个石头的距离 不小于 d
int check(int d)
{
int k = m; //可移除的石头的数目 初始为m
int st = 1; //表示最初的石头
for (int end = 2; end <= n;) //end:当前遍历到的石头的末尾;st从1开始 end便从2开始
{
int disCur = dis[end] - dis[st]; //disCur记录当前距离——st与end石头的间距
while (disCur < d) // 当前st~end的距离小于d的 移除第end石头
{
k--; //可移除的石头的数目 -1
end++; //考虑下一个石头
if (k < 0) //移除的石头的量 已经超过限定的m个 ->当前 d 不可行
return 0;
if (end > n)
{
if (st == 1) //从头到尾 整个的距离都小于d 没有移除的
return 0;
else
return 1; //st开始于石头序列的中间某一位置 那么st向前可移动,向前合并区间,便可以找到满足条件的d
}
disCur = dis[end] - dis[st]; //更新当前的距离
}
st = end; //更新开始石头的位置
end++;
}
return 1;
}
int main()
{
cin >> n >> m;
for (int i = 2; i <= n; i++)
{
cin >> a[i];
dis[i] = a[i] + dis[i - 1];
}
//二分查找满足条件的距离
int lb = 0, ub = 1000 * 1000 + 5; //查找的上下界
while (lb < ub)
{
int mid = (lb + ub) / 2;
if (check(mid)) //check = 1 d小了
lb = mid;
else // d大了
ub = mid - 1;
}
cout << lb;
system("pause");
return 0;
}
小结/补充
- 对于这道题,因为移除石头是动态的过程,所以构造循环尽进行求解的话,循环变量很难构造。
- 对于不定多元的所有元素,也可以进行递归实现。对于每个从根节点到叶子结点的任何一条路径构成一个枚举对象。
- 单纯的贪心算法不可行,反例: