二分作为查找利器很实用:
本篇主要讲述二分理论
二分解题
二分STL解题
二分模板
非递推版本
int search(int x) //二分查找
{
int left=1,right=n;
while(left<=right) //注意这里必须等于
{
int mid=(left+right)/2;
if(x>num[mid])
left=mid+1;
else if(x==num[mid])
{
return mid;
}
else
right=mid-1;
}
}
题目:
01:查找最接近的元素
//二分查找模板
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 100001;
long long int num[maxn];
int n;
int search(int key) //二分查找
{
int left=1,right=n;
int ans;
while(left<=right-2) //注意这里必须等于
{
int mid=(left+right)/2;
if(key>num[mid])
left=mid;
else
right=mid;
}
if(fabs(num[left]-key)<=fabs(num[left+1]-key)&&left<n)
ans = num[left];
else {
ans = num[left+1];
}
return ans;
}
int main()
{
//int n;
long long int m;
long long int tar;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&num[i]);
}
scanf("%lld",&m);
while(m--)
{
scanf("%lld",&tar);
cout<<search(tar)<<endl;;
}
return 0;
}
07:和为给定数
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
int n;
int a[100002];
int m;
int search(int x)
{
int l = x+1;
int r = n;
while(l<=r)
{
int mid = (l+r)/2;
if(a[mid]==m-a[x])
return mid;
else if(a[mid]>m-a[x]) r = mid-1;
else l = mid+1;
}
return -1;
}
int main()
{
int tmp;
int i,l;
int r,mid;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);//选出小的更小
scanf("%d",&m);
for(i=1;i<=n;i++)
{
tmp = search(i);
if(a[tmp]==m-a[i])//输出结果
{
printf("%d %d\n",a[i],m-a[i]);
return 0;
}
}
printf("No\n");//没有
return 0;
}
06:月度开销
题目是要求得开销最小的月度划分的最大开销,不难看出在极端情况(如 每天开销为1,1,50,划分为2个fajo月)时开销最小值是每天开销的最大值(50)。因此,我们可以在读入时找到每天开销的最大值,作为二分查找中的左边界(left)。但是右边界(right)在本题目中并不好找,我们只能尽量将它设大一些,此时列举极端情况(如 每天开销为4,5,6,划分为1个fajo月),则发现最小开销为每天开销的总和。于是我们可以把每天开销的和(sum)作为右边界使用。
接下来是二分查找,此时的mid是作为假定的最小划分的最大开销使用。然后我们需要判断mid是否成立。
判断的方法如下:
尝试在每一天的开销中划分,若该fajo月的开销总额没有超过mid,就继续累加;否则将划分出的fajo月总数加1,并重新累加下一个fajo月。在中途若发现该fajo月的开销已经比mid大,就说明mid是错误(false)的。划分完毕后,比较划分出的fajo月总数和m(要求划分出的fajo月总数),若比m大(严格”>”),说明mid不正确;反之正确。
最后需要改变左右边界值。根据之前判断的结果,若正确,根据题意,要找到最小的划分,所以向下查找(改变right的值);反之,说明mid太小,就向上查找(改变left的值)。
//月份中最大的花费作为左端点,总花费作为右端点进行二分
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100009;
int n,m,maxx,right1,ans;
int a[maxn];
int check(int x)
{
int sum = 0;
int yfen = 1;
for(int i=1;i<=n;i++)
{
if(sum+a[i]<=x)
{
sum+=a[i];
}
else
{
sum = a[i];
yfen++;
if(sum>x) return 0;
}
}
if(yfen<=m)
return 1;
else
return 0;
}
int search()
{
int l = maxx;
int r = right1;
int mid;
while(l<=r)
{
mid = (l+r)/2;
if(check(mid))
{
ans = mid;
r = mid-1;
}
else l = mid+1;
}
return ans;
}
int main()
{
cin>>n>>m;
right1 = 0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
right1+=a[i];
if(a[i]>maxx) maxx = a[i];
}
cout<<search()<<endl;
return 0;
}
10:河中跳房子
左端点为0,右端点为总长度,二分,
根据i,j之间的距离来决定拆除的石板数,依次枚举
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 50020;
int river[maxn];
int L,l,r,n,m;
int mid;
int ans;
int search(int mid)
{
int cnt = 0,wz = 0;
for(int i=1;i<=n;i++)
{
if(river[i]-river[wz]<=mid)
{
cnt++;
}
else wz = i;
}
return cnt;
}
int main()
{
scanf("%d%d%d",&L,&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&river[i]);
}
n++;
river[n] = L;
l = 0;
r = L;
while(l<=r)
{
int mid = (l+r)/2;
ans = search(mid);
//cout<<ans<<endl;
if(ans>m) r = mid-1;
else l = mid+1;
}
cout<<l<<endl;
return 0;
}
二分查找模板 nyoj626
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn = 50001;
int a[maxn],b[maxn];
int n,m;
int search(int key,int* vec,int len)
{
int l = 0;
int r = len-1;
while(l<=r) //注意这里必须等于
{
int mid=(l+r)/2;
if(key>vec[mid])
l=mid+1;
else if(key==vec[mid])
{
return 1;
}
else
r=mid-1;
}
return 0;
}
int main()
{
int m,n,count;
while(~scanf("%d%d",&m,&n))
{
count=0;
if(n==0&&m==0) break;
for(int i=0;i<m;i++)
scanf("%d",&a[i]);
for(int i=0;i<n;i++)
scanf("%d",&b[i]);
if(m>=n)
{
sort(a,a+m);
for(int i=0;i<n;i++)
{
if(search(b[i],a,m))
count++;
}
printf("%d\n",count);
}
else
{
sort(b,b+n);
for(int i=0;i<m;i++)
{
if(search(a[i],b,n))
count++;
}
printf("%d\n",count);
}
}
return 0;
}