题目描述
有一条河,河中间有一些石头,已知石头的数量和相邻两块石头之间的距离。现在可以移除一些石头,问最多移除m块石头后(首尾两块石头不可以移除),相邻两块石头之间的距离的最小值最大是多少。
输入格式
多组输入(<=20组数据,读入以EOF结尾)
每组第一行输入两个数字,n(2<=n<=1000)为石头的个数,m(0<=m<=n-2)为可移除的石头数目
随后n-1个数字,表示顺序和相邻两块石头的距离d(d<=1000)
输出格式
每组输出一行结果,表示最大的点数
样例输入
4 1
1 2 3
样例输出
3
题意:
在河里有n块石头,你可以移动至少m块石头(第一块和最后一块是不能被移走的),求移走至少m块石头后,相邻石头之间的最小距离的最大值。
思路:
由于题目要求最小距离的最大值,就想到二分,这个题要求的就是最小值的极大化;移动的数目肯定要是m才是最优的,不妨设d是已知的,现在来考虑c(d)怎么来写?
(1).循环算出相邻之间的石头,如果小于d,则移动这块石头;否则就算下一个相邻的石头;
(2).最后考虑移动石头的数目如果小于等于m,则枚举长度偏小了;如果移动石头数目大于m,则枚举长度偏大了。
AC(我的代码O(nlogn)):
#include<stdio.h>
#include<algorithm>
using namespace std;
const int MAXN=1e3+5;
int arr[MAXN],ans[MAXN];
int n,m;//n为石头数量,m为可移动数量;
bool solve(int middle)//middle代表最短距离中的最大值
{
int rockNum=0;//记录移动石头数
int st=1;//最初的石头
for(int i=2;i<=n;i++)//i表示结尾的石头
{
if(ans[i]-ans[st]<middle)//如果相邻距离小于middle,则移动石头
rockNum++;
else //否则就更新尾石头
st=i;
}
if(rockNum>m)//如果移动的石头超过m,则枚举长度偏大了
return false;
return true;//枚举长度偏小了
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=2;i<=n;i++)
{
scanf("%d",&arr[i]);//arr数组代表相邻之间的距离
ans[i]=arr[i]+ans[i-1];//ans数组代表从1到i之间的距离
}
int low=0,high=1000*1000+5;//下限为0,上线只要满足l+right大于河长就行
while(low<=high)
{
int middle=(low+high)>>1;
if(solve(middle)) low=middle+1;//由于移除m有很多个解,然而又并不是最优解的,所以我们把长度加一进行二分
else high=middle-1;//更新上限
}
printf("%d\n",low-1);
}
return 0;
}
AC(作者的代码)
#include<stdio.h>
#include<algorithm>
using namespace std;
const int MAXN=1e3+5;
int arr[MAXN],ans[MAXN];
int n,m;//n为石头数量,m为可移动数量;
bool solve(int middle)//middle代表最短距离中的最大值
{
int rockNum=m;//可移动石头数
int st=1;//最初的石头
for(int i=2;i<=n;i++)//i表示结尾的石头
{
for(int dis=ans[i]-ans[st];dis<middle;) //dis表示st和i之间的间距
{//如果dis是小于最大值的则去掉一个石头
rockNum--;//去除一个石头
i++;//然后结尾石头往后移一位
if(rockNum<0) return false;//移除石头超过m,则目标数在右边
if(i>n)
{
if(st==1) return false;//如果所有距离都小于middle时,则在右边继续寻找
else return true;//如果任意之间的距离不都小于middle,则在左边寻找
}
dis=ans[i]-ans[st];//更新相邻距离
}
st=i;//更新起点
}
return true;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=2;i<=n;i++)
{
scanf("%d",&arr[i]);//arr数组代表相邻之间的距离
ans[i]=arr[i]+ans[i-1];//ans数组代表从1到i之间的距离
}
int low=0,high=1000*1000+5;//下限为0,上线只要满足l+right大于河长即可
while(low<high)
{
int middle=(low+high+1)>>1; // 由于移除m有很多个解,然而又并不是最优解的,所以我们把长度加一进行下一次二分
if(solve(middle)) low=middle;//更新下限
else high=middle-1;//更新上限
}
printf("%d\n",low);
}
return 0;
}
在这里作者说他的代码是O(nlogn)的,我分析的是n²logn;
最后说一下关于二分题的思路:
最小值极大化:
//初始化low和high
while(low<high)
{
int mid=(low+high)>>1;
if(c(mid)) low=mid;
else high=mid-1;
}
最大值最小化:
//初始化low和high
while(low<high)
{
int mid=(low+high)>>1;
if(c(mid)) high=mid;
else low=mid+1;
}
c(mid)方法就是我们要根据题意自己写的算法;