二分

二分,是二分查找的简称,又叫折半查找,是一种用于快速查找的工具,也可以说是一种技巧与思想,要有二分的思想,很多算法和优化都用了二分。

二分的思想

首先,来看一道和二分没什么关系,但和二分的思想有关的题目

有一个由n个节点连成的链状线路,最近突然出了故障无法正常工作了。
经过一番研究,已经确定有且仅有一个节点出了故障。根据线路的特点,如果节点i出了故障,那么对于所有节点j(i≤j≤n)都将无法正常工作。
现在你决定找出出故障的节点并修复它。每一个小时,你可以选择连续的至多m个节点,然后测试出每一个节点是否可以正常工作。
由于不知道究竟是哪个节点出了故障,你想知道,在最坏情况下,最少需要多少小时你才能找到那个出故障的节点。
注意:如果你能检测捻推导出坏的节点位置,检测过程就结束了。

这道题,首先想想,最坏的情况是什么呢?那就是必须把每个点都确定一遍,那么,为了最少的次数,就要尽可能的试左右两边分的均匀,这样,能使每次筛去的部分尽可能大。
这道题代码非常简单,但可以很好的理解二分的思想

#include <cstdio>
#define ll long long
ll n,m;
int main() {
	scanf("%lld%lld",&n,&m);
	ll i=1,k=0;
	while(i<n) n=(n-m)/2+1,k++;
	printf("%lld",k);
	return 0;
}

二分的实现

首先,我们必须要规定一个边界,即“那个数的上限 l 和下限 r ”,这样我们才能进行查找。
每次取一个mid的值,如果mid值偏大,那么r=mid-1,反之,l=mid+1
首先来看一个简单的例题一元三次方程求解

有形如:ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,da,b,c,d均为实数),并约定该方程存在三个不同实根(根的范围在-100−100至100100之间),且根与根之差的绝对值 \ge 1≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后22位。

首先,我们知道,如果方程在x1,x2之间有解,那么,f[x1]*f[x2]<=0,由这个我们可以O(N)的复杂度扫出零点所在的区间。
其次,在每个区间内,每次枚举一个mid值,如果f[mid]==0,就直接返回,否则,看他左边还是右边满足上述性质,继续递归下去(其实也可以循环)
具体的代码实现就不难了(注意精度!!!当时调试一早上就因为那个精度)

#include<bits/stdc++.h>
#define FOR(i,n,m) for(int i=n;i<=m;i++)
using namespace std;
double a,b,c,d;
double f(double x){return a*x*x*x+b*x*x+c*x+d;}
void dfs(double _min,double _max,double _mid){
	if(_max-_min<0.0000000001) return;
	if(f(_mid)<0.0000001&&f(_mid)>-0.0000001) {printf("%.2f ",_mid);return;}
	if(f(_min)*f(_mid)<0){dfs(_min,_mid,(_min+_mid)/2);}
	if(f(_max)*f(_mid)<0){dfs(_mid,_max,(_mid+_max)/2);}
} 

int main(){
	cin>>a>>b>>c>>d;
	double hhh,hhhh;
	for(double i=-100;i<=100;i++){
		double l=i;
        double r=i+1;
        if (f(l)==0)
        printf("%.2f ",l);		
        if (f(l)*f(r)<0) dfs(l,r,(l+r)/2);
	}
	return 0;
}

通常情况下,二分都用作最大值最小或是最小值最大的情况
在这里插入图片描述
这道题看着最近距离最远,我们就考虑用二分。
首先,预处理出每个点到最近的一个敌人的距离,每次二分出来一个mid的值,如果下一个点的距离大于等于mid,就继续往下走。如果走不了就使r=mid-1,反之,如果可以通过就使l=mid+1。

#include<iostream>
#include<cstdio>
#include<queue>
#include<utility>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef pair<int,int> p;
const int N=550;
int n,m,a[N][N],d[N][N],vi[N][N],sx,sy,tx,ty,l,r;
int dx[4]={+1,+0,-1,+0},dy[4]={+0,+1,+0,-1};
char ch;
queue<p>q;
bool chk(int lim){
	if(lim>d[sx][sy]) return 0;
	while(q.size()) q.pop();
	FOR(i,1,n)FOR(j,1,m) vi[i][j]=0;
	q.push(p(sx,sy));vi[sx][sy]=1;
	while(q.size()){
		int x=q.front().first,y=q.front().second;
		q.pop();
		if(x==tx && y==ty) return 1;
		FOR(i,0,3){
			int u=x+dx[i],v=y+dy[i];
			if(0<u && u<=n && 0<v && v<=m && vi[u][v]==0 && d[u][v]>=lim) 
				vi[u][v]=1,q.push(p(u,v));
		}
	}return 0;
}
int main(){
	scanf("%d%d",&n,&m);
	FOR(i,1,n)FOR(j,1,m){
		for(ch=getchar();ch!='+' && ch!='.' && ch!='J' && ch!='V';ch=getchar());
		if(ch=='+') a[i][j]=1,vi[i][j]=1,q.push(p(i,j));
		if(ch=='J') tx=i,ty=j;
		if(ch=='V') sx=i,sy=j;
	}
	while(q.size()){
		int x=q.front().first,y=q.front().second;
		q.pop();
		FOR(i,0,3){
			int u=x+dx[i],v=y+dy[i];
			if(0<u && u<=n && 0<v && v<=m && !vi[u][v]) 
				d[u][v]=d[x][y]+1,vi[u][v]=1,q.push(p(u,v));
		}
	}
	l=0,r=n+m-2;
	while(l<r){  //二分
		int md=l+r+1>>1;
		chk(md)?l=md:r=md-1;
	}
	printf("%d",l);
}

还有一个二分的经典题目,tree
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
这时候就有一种神奇的二分了题解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值