河里面的石头+二分

题目描述

有一条河,河中间有一些石头,已知石头的数量和相邻两块石头之间的距离。现在可以移除一些石头,问最多移除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)方法就是我们要根据题意自己写的算法;

发布了39 篇原创文章 · 获赞 1 · 访问量 571

猜你喜欢

转载自blog.csdn.net/qq_45249273/article/details/104397953