二分算法(放在一块看看能不能看出啥)

2022/3/23更新,每一长段冗长的代码下面的那短短的二分函数,真的不用考虑太多边界啥的,死循环啥的。枉我以前看到二分就吓得躲开。浪费了多少时间呀!方法比努力重要!!

二分题目及其总结.

二分题目及其总结.
mp.lower_bound(value);
对数组进行二分查找,返回下标
lower_bound(a,a+n,value)-a;
1、贷款原值为n,每个月实际要还m,k个月还完,利率为x
可以得到等式关系pow(1.0/(1.0+x),k)==1-(n/m)*x)

bool find(double x){
	return (pow(1.0/(1.0+x),k)>=1-(n/m)*x);//如果大于 向左查找,反之向右查找 
} 

2、之前找两个序列中重叠的值老是要循环遍历,想先排序增加效率,还要求你按在其中一个原序列中的顺序输出
这下好了,其中一个保持原序列,另一个先排序再搜索(归并搜索,二分都建立在排序的基础山),通过map数组记录该有序序列中出现的元素,按照第一个原序序列,分别判断是否在mp数组中出现过。
mp[a[i]]!=0
mp.find(a[i])!=string::npos说明找到了
mp.find(a[i])没找到返回-1(非零的),不能通过if(mp.find(a[i]))判断
string:npos是个特殊值,说明查找没有匹配

03:矩形分割

03:矩形分割

理解题目还挺重要的 ,不然一直在那儿白费脑力
描述
平面上有一个大矩形,其左下角坐标(0,0),右上角坐标(R,R)。大矩形内部包含一些小矩形,小矩形都平行于坐标轴且互不重叠。所有矩形的顶点都是整点。要求画一根平行于y轴的直线x=k(k是整数) ,使得这些小矩形落在直线左边的面积必须大于等于落在右边的面积,且两边面积之差最小。并且,要使得大矩形在直线左边的的面积尽可能大。注意:若直线穿过一个小矩形,将会把它切成两个部分,分属左右两侧。

一开始看题,既要左右面积差值最小又要大矩形左侧面积最大,像极了数学里高深的优化问题,难不成还得来个多元线性回归?!
不重叠,说明 要求大矩形左侧面积尽可能大,只是建立在 已经找到小矩形们左右面积差最小的位置 这一条件之上,如果找到的位置x可以向右移动并且不改变左右侧小巨星们的面积(怎么才能实现这一条件呢?自然是这个位置位于两个小矩形之间的留白处,那么就将x进一步向右调整到下一个小矩形的左侧,跨过整个留白区, 或者是跨国最后一个小矩形与大矩形右边界的留白区),这种操作可以视作两种情况,也可合二为一

2023更新:左侧面积大于等于右侧面积(条件1)且面积差尽可能地小(条件2)==找到最小的满足(条件1)的分界点
优先考虑以上两个条件,在以上两个指标都不变的基础上,要使得左侧面积尽可能大,自然就是找到以上分界点后,向右挪过空白区

		while(areaD(x)==areaD(x+1)&&x<a){
		x++;
	}//x可以取到大矩形右边界a 
	cout<<x;
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
	ll a;//大正方形右上角坐标 
	ll n;//n个小矩形
//int lx,ly,w,h;//每个小矩形左上角坐标和宽、高
//	int a[MAX][4];
	struct matrix{
		ll l,u,w,h;//左上宽高 
	}m[10001]; 
	ll Size=0;//总面积 
ll areaD(int x){
	ll leftS=0;
	for(int i=0;i<n;i++){
		if(m[i].l<x){
			if(m[i].l+m[i].w<=x)leftS+=m[i].w*m[i].h;
			else leftS+=(x-m[i].l)*m[i].h;
		}
	}
	ll differ=leftS-(Size-leftS);
	return differ;
}
ll search(int left,int right){
		ll mid;
		while(left<=right){
		mid=left+(right-left)/2;
		if(areaD(mid)>0)right=mid-1;
		else if(areaD(mid)<0)left=mid+1;
		else return mid;
	}
	return left;
}
int main(int argc, char** argv) {
	cin>>a>>n;
	for(int i=0;i<n;i++){
		cin>>m[i].l>>m[i].u>>m[i].w>>m[i].h;
		Size+=m[i].w*m[i].h;
	} 
	ll  x=search(0,a);
	while(areaD(x)==areaD(x+1)&&x<a){
		x++;
	}//x可以取到大矩形右边界a 
	cout<<x;
	return 0;
}
ll search(int left,int right){
		ll mid;
		while(left<right){
		mid=left+(right-left)/2;
//		if(areaD(mid)>0)right=mid-1;
//		else if(areaD(mid)<0)left=mid+1;
//		else return mid;
		if(areaD(mid)>=0)right=mid;
		else left=mid+1;
	}
	return left;
}

总而言之:
1、能找到面积相等的位置,就继续探寻是否能跨过一个留白区,找到满足“左侧面积 = =右侧面积”的最大整数值
2、对于右侧面积为0的:最终值 = 大矩形的边界 r
3、若不属于以上两种情况,就通过while(left<=right)这种二分寻找跳出循环后的left,就是使左右侧面积最接近的位置

还有一个迷惑问题,search函数中将else if改成if,结果惊人1000???

接下来关注到二分查找的细节

二分查找的截止判断条件

  1. while(left<=right)……

  2. while(left<right)……

  3. while(left<right-1)……

    找到了mid倒无甚区别,,如果没找到还需要依赖left与right的final值来确定与所求值最接近的数据的一个范围
    NO.3:
    跳出循环后,left == right-1,left与right紧紧相邻
    需要二分枚举的答案是整数,所以可以用这个方式结束,
    像这题,还需要分别判断left 和 right哪个更能使左右小矩形们面积之差靠近
    并且在循环体内部
    if (sum>0) r=mid;
    else if (sum<=0) l=mid;
    else if (sum==0) {printf(“%d”,mid);return 0;}
    左右边界值的修改是这样进行的
    NO.1
    跳出循环后
    在循环内部这样修改边界值
    while(left<=right){
    mid=left+(right-left)/2;
    if(areaD(mid)>0)right=mid-1;
    else if(areaD(mid)<0)left=mid+1;
    else return mid;
    }
    return left;

    NO.2
    while(left<right)……
    在循环内部这样修改边界值
    if(leftS<=rightS) //为了尽量让k靠右,需要用小于等于
    {
    l=mid;
    }
    if(leftS>rightS) //为了尽量减少面积差
    {
    r=mid;
    }

    在循环体内部,可以进行最后一次二分进行判断
    if(r-l==1) //经过二分后的最后判断

    NO.1、2都默认跳出循环之后/最后一次循环中,left是最接近寻找的值
    如果mid可能包括要找的值和其他,那么修改边界值(目的是继续寻找边界,边界就要取上一次的mid

while(l<r){/*lower_pound()二分求下界*/
		mid=l+(r-l)/2;
		if(2*sa[mid]>=sa[R])r=mid;//必须如此,不可r=mid-1;亲测错误
		else l=mid+1;
	}
	return l;
}
 NO.1、2都默认跳出循环之后/最后一次循环中,left是最接近寻找的值
 也就是要寻找的值可能会出现在边界值里
 如果将NO.1的mid+/-1改成mid。将NO.2的mid+1改成mid,都会超时
 好的,乱了
 关于修改边值条件,到底是mid+/-1 还是 mid, 要回到二分法最初的思想,就是在target出现的更小的范围内继续二分,也就是说,接下来划定的范围无论如何一定要包括target 但是已经排除的范围不能再加到下一次二分的范围。
 这题target是areaD(mid)==0,或者是areaD(mid)>0的范围里取一个 最接近mid的
 
 一、
 每次二分都是奔着mid=0去的,因此mid不满足条件,就不用再加入下次二分的范围内
 最后一次二分,left==right=mid,mid还不满足条件,那必定是mid-1小于0,mid+1大于0,根据题意我们选的是后者
 这种二分方式,循环结束后left是比所求得target稍大得那个
 二、
 ,mid小于等于0包含了mid==0,mid也许就是最后的target,故需要加入下次二分的范围内。 每次二分其实是为了找到mid>0得最小mid去的,因此mid>0符合所求,故加入下一次二分范围。
 不同于NO3的是跳出循环后,mygod
 三、
 每次二分其实是为了找到mid>0得最小mid去的,因此mid>0符合所求,故加入下一次二分范围,mid小于等于0包含了mid==0,mid也许就是最后的target,故需要加入下次二分的范围内
 跳出循环后,left<right并相邻,并不知道谁更靠近target所以要判断比较

05:派

05:派

#include <bits/stdc++.h>
using namespace std;
//#define pi 3.14
const double pi=acos(-1.0);	
//π值用cmath头文件中的acos(-1.0)也可以求出来。
#define MAX 10001
	double r[MAX];
	double s[MAX];
		int n,f;
int enough(double x){//假设每个人分半径x 
	int cnt=0;
	double si;
	for(int i=0;i<n;i++){
		cnt+=floor(s[i]/x);
	}
	return cnt-(f+1);//enough(x)<0代表每人x不够分 
}
double search(double left,double right){
		double ans;//用于记录满足里面最大的 
		while(right-left>1e-5){//每个人分到的最大派的半径范围里找enough且最大的那个 
		double mid=left+(right-left)/2 ;
/*		if(enough(mid)<0)right=mid-1;
//		else if(enough(mid)>=0)left=mid;这是满足的,要在里面找最大的 ,
//可不可以像划分举行那样先找到刚好enough的再向右移动呢,不好!这样移动不如继续二分
//失去了二分本来的节约时间的目的 
		else if(enough(mid)>0)left=mid+1;
		else return mid;//找到刚好够分的 */
		if(enough(mid)>=0){
			ans=mid;//不断右分过程中找最大的 
			left=mid;
		}
		else right=mid-0.00001; //不能是mid减1噢,这可是浮点数 
	}
//	return left; //这里则总能找到mid,因为是double,不用思索跳出循环后left咋样
	if(enough(left)>=0) ans=left;
	return ans;
}
int main(int argc, char** argv) {
	cin>>n>>f;
	//int m=f+1;//n块派分给m个人,每人分一整个派/一个派的一块,每人大小相等
//	int maxS=0;
	for(int i=0;i<n;i++){
		cin>>r[i];
		s[i]=pi*r[i]*r[i];
//		if(s[i]>maxS)maxS=s[i];
	} 
	sort(s,s+n);
	cout<<fixed<<setprecision(3)<<search(0,s[n-1]);
	return 0;
}

double search(double left,double right){
		while(right-left>1e-5){
		double mid=left+(right-left)/2 ;
		if(enough(mid)>=0){
			left=mid;
		}
		else right=mid; //不能是mid减1噢,这可是浮点数 
	}
	return left;
}

//一、pi的值acos 二、double类型的比较 要用1e-1 三、每个人可以分一整个派多加
//别的派吗,二分的最大值是最小的那块派面积才对呀,好吧,经过提交还是最大的那块,
//不理解 二分要求的是面积,那就用面积二分吧,本来就是再0.0000001那么小的间距二分,
//根本承受不起半径计算面积再考虑精度带来的误差

06:月度开销

题目链接


#include <bits/stdc++.h>
using namespace std;
#define MAX 100010 //眼神不好就直接赋值题干吧,runtime error
int a[MAX];
int n,m;//代表总天数和“月”数 
int adequate(int x){//x是待试的最小的fajio月度开销 
//	int month=0;
int month=1;
//	//啊啊啊啊啊,第一个月压根没考虑进去呀 分好了第一个月后month还为0 
	int temp=0;//累加每个月的开销 
	for(int i=0;i<n;i++){
		if(temp+a[i]<=x){//1、等于也是可以算在这个月的呀 
			temp+=a[i];
		}
		else {
			temp=a[i];//把第i天的开销算在下一个月 
			month++;
		}
	}
	if(month>m)return 0;//说明定下的月度开销x 
	else return 1; 
	//2、month大了说明暂定的月度开支的偏小了,不行
	记住满足条件是 这些月份都能包下,甚至更少的月
}
int search(int left,int right){
	int ans=0; 
	while(left<=right){
		int mid=left+(right-left)/2;
		if(adequate(mid)){
//				printf("niah2\n");
			right=mid-1;//因为mid可能是符合条件的值 
//			但没关系,ans已经记录了mid,如果接下来没找到,那就是ans了 
			ans=mid;//在符合条件的范围里头找最小的 
		}
		else {
			left=mid+1; //上述分析mid娶不到
		} 
	}
	return ans;
}
int main(int argc, char** argv) {
	cin>>n>>m;
	int maxx=0;
	int sum=0;
	for(int i=0;i<n;i++){
		cin>>a[i];
		if(a[i]>maxx)maxx=a[i];
		sum+=a[i];
	}
	cout<<search(maxx,sum)<<endl;
	return 0;
}
int search(int left,int right){
	while(left<right){
		int mid=left+(right-left)/2;
		if(adequate(mid)){
			right=mid;
		}
		else left=mid+1;
	}
	return left;
}

些许离谱,离谱到有些难以置信

  1. 对于month的初始化的值是0还是1,思维定势地初始化为0,而实际上其实就对第一个月分配天数。
  2. 对于题目给出的数据范围,少看了一个0导致出现runtime error,眼神不好,请求手手直接赋值题干吧
    关于在oj上出现Runtime error 的情况
    这个挺好,虽然只是字面上提示了我,与我犯的错误本质不同,但学到了:在函数里使用了太大的数组,函数里的数组作为局部变量存放在栈区,也有可能会Runtime error,解决方法是在全局区开这个数组
  3. 关于二分的条件函数adequate,其实返回的true和false对应的情况是谁并不重要,本就是用来判断下一次二分的范围的。但为了代码的含义考虑,最好true还是代表满足二分所需的条件。

10:河中跳房子

#include <bits/stdc++.h>
using namespace std;
#define MAX 50010
typedef long long ll;
	ll d,n,m;
	ll a[MAX];//每个岩石与起点的距离 
bool adequate(int x){//一次条约过程中的 最短跳跃距离 
		int cnt=0;//移除几块石头 
		int now=0;
		for(int i=1;i<=n+1;i++){
			if(a[i]-a[now]>=x){//够得着最短条约距离继续往后跳,
//			够不着最小条约距离就移除石头之后就能够着了 
				now=i;
				cnt++;
			}
		}
//		if(cnt>n-m+1) //太短了,要往长处加 ,剩下的石头太多了 
		if(cnt<=n-m)return false;
		else return true;
	} 
int search(int left,int right){
		int ans=0;
		while(left<=right){
			int mid=left+(right-left)/2;
			if(adequate(mid)){
				
					left=mid+1;
					ans=mid;
			}
			else{
			
			right=mid-1;
			} 
		}
		return ans;
	}
int main(int argc, char** argv) {
	cin>>d>>n>>m;
	a[0]=0; 
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	a[n+1]=d; 
	cout<<search(1,d);
	return 0;
}

思考方式,进入
int k=n-m+1;//移除m个岩石后起点与岸之间还剩 k段距离 ,求这k段距离里
求最大月度开销的最小值,月的划分方式很多种,每种方式得到一系列 阅读开销,求这一系列阅读开销里最大的值最小的那种方式
这里求最长可能的最短跳跃距离,划分距离的方式很多种(已知划分为几段)
每种方式得到一系列条约距离,其中总存在一个最小的距离,求哪种划分方式使这个最小距离最大

2456:Aggressive cows

这题算 最短距离的最大值和河中跳房子是简直一模一样了,其中,不管要求最短距离的最大值,还是求最大阅读开销的最小值,在adequate函数中,传进去的参数就要肯定得当作最短距离或者是最大月度开销,假设这个成立,再根据这个条件去划范围,找另一个最短距离或者是最大月度开销。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MAX 100010
int n,m;
ll a[MAX];
bool adequate(int x){//此时我应该肯定!x就是最短距离!再做事 
		int cnt=1;//牛的数量 第一个马厩肯定放一个 
		int interval=0; 
		for(int i=1;i<n;i++){
			interval+=a[i]-a[i-1];
			if(interval>=x){
				cnt++;//牛放在i这个马厩
				interval=0;//放好了一头牛,接下来的距离又变成0 
			} 
			if(cnt==m)return true;
		} 
//		if(cnt<m)return false;//放不下n头牛,最短距离太大了
//		else	return true; 
//		if(cnt==m)return true;
		return false;
	} 
int search(int left,int right){
		int ans=0;
		while(left<=right){
			int mid=left+(right-left)/2;
			if(adequate(mid)){
				ans=mid;
				left=mid+1;
			}
			else{
				right=mid-1;
			} 
		}
		return ans;
/**********************************
**或者不用ans,最后返回left-1,跳出循环后的left比最后得到的mid大1码!!!**
***********************************/
	}
int main(int argc, char** argv) {
	cin>>n>>m; //求最大的最小距离 
	memset(a,0,sizeof(a));
	for(int i=0;i<n;i++){
//		cin>>a[i];Huge input data,scanf is recommended.
	scanf("%d",&a[i]);
	} 
	sort(a,a+n);
	int r=a[n-1]-a[0];
	cout<<search(0,r);//两头牛之前距离的最大范围 
	return 0;
}

由于求的是最短距离的最大值,因此

07:和为给定数

十分和八分,自行体会,不理解 英语作业没写啊啊啊啊啊啊啊

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MAX 100010
ll n,m;
ll a[MAX];
ll x;
bool search(int left,int right){
		while(left<=right){
			ll mid=left+(right-left)/2;
			if(a[mid]<x){
				left=mid+1;
			}
			else if(a[mid]>x){
				right=mid-1;
			} 
			else{
				return true;	
			}
		}
		return false;
	}
int main(int argc, char** argv) {
//	scanf("%d",&n);
cin>>n;
	for(int i=0;i<n;i++){
//	scanf("%d",&a[i]);
cin>>a[i];
	} 

cin>>m;
	sort(a,a+n);
//	ll ri=a[n-1];
	for(int i=0;i<n-1;i++){
		x=m-a[i];
		if(search(i+1,n-1)){//寻找m-a[i],从a[i+1]开始找 
	cout<<a[i]<<" "<<x;
			return 0;
		}
		
	}
		printf("No");
	return 0;
}

八分,十分运行耗得内存小
一个传了数组下标进去二分,一个传了数组的值,前者十分儿,Why?

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MAX 100010
ll n,m;
ll a[MAX];
ll x;
bool search(int left,int right){
		while(left<=right){
			ll mid=left+(right-left)/2;
			if(mid<x){
				left=mid+1;
			}
			else if(mid>x){
				right=mid-1;
			} 
			else{
				return true;	
			}
		}
		return false;
	}
int main(int argc, char** argv) {
//	scanf("%d",&n);
cin>>n;
	for(int i=0;i<n;i++){
//	scanf("%d",&a[i]);
cin>>a[i];
	} 

cin>>m;
	sort(a,a+n);
	ll ri=a[n-1];
	for(int i=0;i<n-1;i++){
		x=m-a[i];
		if(search(a[i+1],ri)){//寻找m-a[i],从a[i+1]开始找 
	cout<<a[i]<<" "<<x;
			return 0;
		}
		
	}
		printf("No");
	return 0;
}

不行了,再来一波!!!

Cable master

浮点型二分法转化为整数进行:新技巧:以后对于这类的浮点数的二分时,将其转化成整型进行二分;(如果扩大倍数的,之后别忘再缩回来))

在这里插入图片描述
大致意思是:有n根电缆,需要裁成k根长度相同的电缆,问这个长度最大取多少 ?
困惑之处: 为什么search函数返回l和mid都wa, 跳出循环时r就是无限接近于l呀???
学习笔记:
【1】、直接保留两种小数的做法会四舍五入 ,
解决:先化作整数用floor取整,再恢复小数点位置。
【2】、
eps不能设置的太小,不然如果小于double的精度的话会导致死循环问题。一般eps设为1e-7即可。
【3】、
这题用cin输入会 tle,小老师说朝时就换成scanf,挺管用,有时间还是得好好看看 cin.tie(0)怎么用
易错示警:
浮点型二分法转化为整数进行,扩大倍数之后要缩回来,这就涉及到整数转化成浮点型double(search(1,maxx)/100.0)
其中这个100.0 是非常必要的,由于要保留两位小数所以一开始将浮点数扩大成了100倍的整数,因此
整数的每一位都是有效数字
。如果直接两个整型数据相除,轻易就会失去有效数据,这时就需要让这种运算的精度提升到浮点型的运算精度,不会失去有效数据。**

	printf("%.2f",floor(search(0,maxx)*100)/100);
//	cout<<search(0,maxx);//2.005
//		printf("%.2f",search(0,maxx));这种直接保留两种小数的做法会四舍五入 
//		cout<<fixed<<setprecision(2)<<search(0,maxx);
#include <stdlib.h>	
#include <stdio.h>
#include <iostream>	
#include <algorithm>
#include <math.h>
#include <iomanip>		
using namespace std;
double a[10005];
int n,k;//有n根电缆,需要裁成k根长度相同的电缆,问这个长度最大取多少 
bool adequate(double x){
	int cnt=0;
	for(int i=0;i<n;i++){
		cnt+=int(a[i]/x);
	}
	if(cnt>=k)return true;
	else return false;
}
double search(double l,double r){
	double mid;
	while(r-l>1e-10){//	while(fabs(r-l)>1e-7){
		mid=(l+r)/2;
		if(adequate(mid)){
			l=mid;
		}
		else r=mid;
	}
	return r;//不明白,为什么返回l和mid都wa,跳出循环时r就是无限接近于l呀??? 
}
int main(){

	 cin>>n>>k;
	 for(int i=0;i<n;i++){
//	 	cin>>a[i];
		scanf("%lf",&a[i]);
	 }
	 sort(a,a+n);
	 double maxx=a[n-1];
	printf("%.2f",floor(search(0,maxx)*100)/100);
//	cout<<search(0,maxx);//2.005
//		printf("%.2f",search(0,maxx));这种直接保留两种小数的做法会四舍五入 
//		cout<<fixed<<setprecision(2)<<search(0,maxx);
    return 0;
}
#include <stdlib.h>	
#include <stdio.h>
#include <iostream>	
#include <algorithm>
#include <math.h>
#include <iomanip>		
using namespace std;
int a[10005];
int n,k;//有n根电缆,需要裁成k根长度相同的电缆,问这个长度最大取多少 
bool adequate(int x){
	int cnt=0;
	for(int i=0;i<n;i++){
		cnt+=int(a[i]/x);
	}
	if(cnt>=k)return true;
	else return false;
}
int search(int l,int r){
	int ans=0;
	while(r>=l){
		int mid=(l+r)/2;
		if(adequate(mid)){
			l=mid+1;
			ans=mid;
		}
		else r=mid-1;
	}
	return ans;
}
int main(){

	 cin>>n>>k;
	 double x;
	 for(int i=0;i<n;i++){
	scanf("%lf",&x);
		a[i]=int(x*100);
	 }
	 sort(a,a+n);
	 int maxx=a[n-1];
	 cout<<fixed<<setprecision(2)<<double(search(1,maxx)/100.0);
    return 0;
}

也不用强求啦,浮点型的二分也不过而已(主要是懒得想转化为整数型是否更简便了

Strange fuction

在这里插入图片描述

#include <bits/stdc++.h>		
using namespace std;
double fun(double x,double y){
	return 6*pow(x,7)+8*pow(x,6)+7*pow(x,3)+5*pow(x,2)-x*y;
}
double f(double x,double y){
	return 42*pow(x,6)+48*pow(x,5)+21*pow(x,2)+10*pow(x,1)-y;
}
	double y;
double search(double l,double r){
		double mid=0;
		while(r-l>1e-6){
		mid=(l+r)/2;
		double temp=f(mid,y);
//		if(temp<=1e-6){
//			ans=mid;
//			return ans;
//		}
//		else 
		if(temp>0){
			r=mid;
		}
		else if(temp<0){
			l=mid;
		}
	}
		return mid;
}
int main(){
	int t;
	cin>>t;
	while(t--){
		cin>>y;
		double l=0;
	double r=100.0;
	cout<<fixed<<setprecision(4)<<fun(search(l,r),y)<<endl;
	}
    return 0;
}

The Frog’s Games

在这里插入图片描述

#include <bits/stdc++.h>		
using namespace std;
typedef long long int ll;
ll a[500005];//已知每块石头距离岸边的距离,求每段距离存放 
ll d[50005];
ll L,n,m;//河宽L,n快石头,最多跳m次
bool adequate(int x){//假设 最大条约举例 为x,看看m次能否跳过河水 
if(m*x<L)return false;
	int cnt=0;//跳多少次 
	ll s=0;
	for(int i=1;i<=n+1;i++){
		if(d[i]>x)return false;
		s+=d[i]; 
		if(s>x){
			i--;
			cnt++;
			s=0;
		if(cnt>=m)return false;
		}
	} 
	return true;
} 
ll search(int l,int r){
	ll ans=0; 
	while(l<=r){
		int mid=l+(r-l)/2;
		if(adequate(mid)){//如果neng过河,最大条约距离还可以缩小 
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	return ans;
} 
int main(){//求最小的最大条约距离 

	 while(cin>>L>>n>>m){
	 		memset(a,0,sizeof(a));
	 		memset(d,0,sizeof(d));
	 	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		 } 
		 sort(a+1,a+n+1);//n块石头从进到远排好序
		 for(int i=1;i<=n;i++){
		 	d[i]=a[i]-a[i-1];
		 } 
		 d[n+1]=L-a[n];//n快石头,n+1段距离 
		 
	 	cout<<search(1,L)<<endl;
	 } 
    return 0;
}
#include <bits/stdc++.h>		
using namespace std;
typedef long long int ll;
ll a[500005];//已知每块石头距离岸边的距离,求每段距离存放 
ll L,n,m;//河宽L,n快石头,最多跳m次
bool adequate(int x){//假设 最大条约举例 为x,看看m次能否跳过河水 
if(x*m<L)return false;
	int cnt=0;//跳多少次 
	int pos=0;
	for(int i=1;i<=n+1;i++){
		//if(a[i]-a[i-1]>x)return false;//一段距离就已经大于最大条约距离了,肯定不行 
		if(a[i]-a[pos]>x){
		if(i-pos==1)return false;
			cnt++;
			i--;
			pos=i;
			if(cnt>=m)return false; 
		}

	} 
	 return true; 
} 
int search(int l,int r){
	int ans=0; 
	while(l<=r){
		int mid=l+(r-l)/2;
		if(adequate(mid)){//如果neng过河,最大条约距离还可以缩小 
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	return ans;
} 
int main(){//求最小的最大条约距离 
	 while(cin>>L>>n>>m){
	 		memset(a,0,sizeof(a));
	 	for(int i=1;i<=n;i++){
			//cin>>a[i];
			scanf("%d",&a[i]);
		 } 
		 sort(a+1,a+n+1);//n块石头从进到远排好序
		a[n+1]=L;
		 
	 	cout<<search(1,L)<<endl;
	 } 
    return 0;
}

真的是,以为这里错了那里错了,试错到最后,发现根本不管他们的事情。
sort函数为数组元素排序, sort(a+1,a+n+1);与 sort(a,a+n);
仅此而已。。。卒

小总结们

目的总是取到一个最合适的值。
首先,找到取值的范围,在该范围内进行二分。
判断取值是否满足题意条件
……

整数二分模板一共有两个,分别适用于不同情况。
算法思路:假设目标值在闭区间 [ l , r ] [l, r] [l,r]中, 每次将区间长度缩小一半,当 l = r l = r l=r时,我们就找到了目标值。

模板一:(再也不用为边界的处理而头痛了)
当我们将区间 [ l , r ] [l, r] [l,r]划分成 [ l , m i d ] [l, mid] [l,mid] [ m i d + 1 , r ] [mid + 1, r] [mid+1,r]时,其更新操作是 r = m i d r = mid r=mid或者 l = m i d + 1 l = mid + 1 l=mid+1;,计算 m i d mid mid时不需要加1。

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

模板二:
当我们将区间 [ l , r ] [l, r] [l,r]划分成 [ l , m i d − 1 ] [l, mid - 1] [l,mid1] [ m i d , r ] [mid, r] [mid,r]时,其更新操作是 r = m i d − 1 r = mid - 1 r=mid1或者 l = m i d l = mid l=mid;,此时为了防止死循环,计算 m i d mid mid时需要加1。

边界的处理:
死循环的解释是:例如二分区间缩小到 l=3,r=4 时,l=mid ,mid的算法一定是向上取整,否则一直取的是3
或者解释为,要保持一致的区间分割方式

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

这两种情况就是一个是从小到大找,一个是从大到小找。因为二分的前提是要有序(一般都是,搜索的范围中值是升序的,寻找满足条件的最小值,那么当adequate(mid),对应的就是 模板一 r=mid,
相反,如果要找满足条件的最大值,那么当adequate(mid),对应的就是 模板二 l=mid

浮点数二分

bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;//不能是mid减1噢,这可是浮点数,1是精度的巨大倍 
    }
    return l;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值