二分,是二分查找的简称,又叫折半查找,是一种用于快速查找的工具,也可以说是一种技巧与思想,要有二分的思想,很多算法和优化都用了二分。
二分的思想
首先,来看一道和二分没什么关系,但和二分的思想有关的题目
有一个由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条白色边的生成树。
这时候就有一种神奇的二分了题解