二分算法入门

二分算法入门

一、基本知识

  • 本质:找区间边界,通过满足特定的条件最终找到相应区间,也可以用来找边界(找左右边界时有区别)
  • 优点:相比于遍历每个数据的时间复杂度 O ( n ) O(n) On,二分算法可以达到 O ( l o g n ) O(logn) Ologn的时间复杂度

二、模板

  • HDU 2578 为例
    题意: 给定n个正整数和一个整数k。计算方程x+y=k有多少个不同的解。x和y必须在给定的n个整数中。两种解决方案是不同的。
    输入: 第一行包含一个整数T,然后是T行。每种情况以两个整数n(2<=n<=100000)和k(0<=k<2^31)开始。下一行包含n个整数。
    输出: 对于每种情况,输出方程的解的数目。
#include<algorithm>
#include<iostream>
#include<stdio.h>
using namespace std;
const int maxn=1e6+9;
int a[maxn]; 
int n,k,ans,T;
bool check(int x)//模板
{
	int l=1,r=n;
	while(l<r)   //但有时候条件会变 eg.l+1<r(避免死循环) r-l<1e-3(精度类问题)
	{            //具体问题具体分析
		int mid=(l+r)/2;
		if(a[mid]==x)  return 1;  //找到方程的解
		else if(a[mid]>x) r=mid;  //mid过大 缩小右边界
		else l=mid+1;  //mid过小 缩小左边界
	}
	return 0;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&k);
		ans=0;
		memset(a,0,sizeof a);  //初始化数组
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		sort(a+1,a+n+1);
		for(int i=1;i<=n;i++)
		{
			if(check(k-a[i]))
			{
				ans++;
				if(a[i]==a[i-1]&&i!=1)   //避免重复的解
				   ans--;
			}
		}
		printf("%d\n",ans);
	}
 } 
  • 注:不同题目模板的区别:
    l = m i d l=mid l=mid的时候, m i d = ( r + l + 1 ) / 2 mid=(r+l+1)/2 mid=(r+l+1)/2 ; r = m i d − 1 r=mid-1 r=mid1;
    r = m i d r=mid r=mid的时候, m i d = ( r + l ) / 2 mid=(r+l)/2 mid=(r+l)/2 ; l = m i d + 1 l=mid+1 l=mid+1;
    l l l取到 m i d mid mid的时候,要避免整数除法向下取整而造成死循环,所以要+1。
    erfen

三、函数

头文件中有个叫binary_search 的函数,是通过二分查找的方式来寻找某区间内特定的值。
eg. binary_search(a+first,a+end,value);
如果在a数组中下标[first,end)范围内 存在任一元素和value值相等,则返回true,否则返回false .
优点:代码量少,时间复杂度低(log级别).
这个函数是基于二分查找的,可以用于判断查找值是否存在 。而且也不一定要是数组,容器的迭代器也可以。像这样基于二分查找的函数还有很多。
二分
科普传送门stl中基于二分查找的相关函数

四、例题

1、HDU 2199
  • 题意: 给定方程8x^ 4+7x^3+2x ^2+3x+6==Y,找到它在0到100之间的解;
  • 输入: 输入的第一行包含一个整数T(1<=T<=100),表示测试用例的数量。接着是T行,每行有一个实数Y(fabs(Y)<=1e10
  • 输出: 对于每个测试用例,有解则输出(精确到小数点后4位),如果0到100之间的方程没有解,则输出“No solution!”
  • 思路: 这题因为是实数需要控制精度,一开始没什么想法,思维有些局限了,后来过了一会儿才想起来可以用两个y值的差值的绝对值来控制精度,不断逼近答案。

Sample Input
2
100
-4

Sample Output
1.6152
No solution!

#include <cstdio>
#include <cmath>
double f (double x)  
{
    return 8 * pow(x, 4) + 7 * pow(x, 3) + 2 * pow(x, 2) + 3 * x + 6;
}
int main()
{
    int T;
    double xl, xr, xmid, y, yl, yr, ymid;
    scanf("%d", &T);
    while(T--)
    {
        xl = 0;
        xr = 100;        
        scanf("%lf", &y);  
        yl = f(xl) - y;
        yr = f(xr) - y;
        if (yl > 0 || yr < 0)
            printf("No solution!\n");
        else
        {
            while (fabs(yl - yr) >= 0.00001)
            {
                xmid = (xl + xr) / 2;
                ymid = f(xmid) - y;
                if (ymid >= 0)
                    xr = xmid;
                else
                    xl = xmid;
                yl = f(xl) - y;
                yr = f(xr) - y;
            }
            printf("%.4lf\n", xmid);
        }
    }
    return 0;
}
2、POJ 2456
  • 题意: 有N个牛舍于X1、…、Xn(0 <<Xi<= 1000000000)的直线上
  • 输入: 第1行:两个空格分隔的整数:N和C;接下来N行:每行包含一个整数表示牛舍位置
  • 输出: 一个整数:最小距离的最大值
  • 思路: 把牛舍之间的距离作为二分的对象,写个check函数试验该距离,把牛依次放进去看看能不能放下,直到出现一个值作为临界值刚好可以放得下所有牛,那这就是最小距离的最大值了

Sample Input
5 3
1
2
8
4
9

Sample Output
3

#include<algorithm>
#include<iostream>
#include<stdio.h>
using namespace std;
const int maxn=1e9;
int n,c;
long long a[100010];
bool check(int s)
{
	int p=0;
	for(int i=1;i<c;i++)
	{
		int q=p+1;
		while(q<n&&a[q]-a[p]<s)   //需要满足右边界不越界并且两牛舍距离<s 
		   q++;   //寻找最大值 
		if(q==n)  return 0;   //右边界越界了 即牛放不下 则返回0 
		p=q;   //两牛舍距离>=s 则左区间更新 缩小范围来逼近临界点 
	}
	return 1; 
}
int main()
{
	while(~scanf("%d%d",&n,&c))
	{
		for(int i=0;i<n;i++)
	        scanf("%lld",&a[i]);
	    sort(a,a+n);
	    int l=0,r=maxn;
	    while(l+1<r) //用二分法不断寻找合适的距离
	    {
	    	int mid=(l+r)/2;
	    	if(check(mid))  l=mid;
	    	else  r=mid;
		}
		printf("%d\n",l);
	}
 } 
3、POJ 3258
  • 题意: 奶牛们在河里从一块石头跳到另一块石头。在一条长而直的河流上,起点有一块石头,终点有另一块石头,距离起点有L个单位(1≤L≤1e9)。在河流的起点和终点岩石之间,出现了N(0≤N≤50000)多个岩石,每个岩石与起点之间的积分距离为Di(0<Di<L)。每头母牛依次从起点的岩石开始,并试图到达终点岩石,只从一块岩石跳到另一块岩石。现在农夫计划移走几块石头,以增加母牛必须跳到终点的最短距离。他知道不能移除开始和结束的岩石,但他计算出他有足够的资源移除多达M个岩石(0≤M≤N)。试确定在移除最佳的M块岩石后,奶牛必须跳跃的最短距离的最大值。
  • 输入: 第1行:三个空格分隔的整数:L、N和M;接下来N行:每行包含一个整数,表示某个岩石离起始岩石有多远。没有两块石头的位置相同。
  • 输出: 第1行:一个整数,是牛在移除M块石头后必须跳跃的最短距离的最大值
  • 思路: 一开始看到的时候,就觉得跟上一题差不多,换汤不换药,就是从牛舍变成石头而已

Sample Input
25 5 2
2
14
11
21
17

Sample Output
4

#include<algorithm>
#include<iostream>
#include<stdio.h>
using namespace std;
const int maxn=1e9;
int L,n,m;
long long a[50010];
bool check(int s)
{
	int ll=0,rr=0;
	for(int i=0;i<=n;i++)
	{
		if(a[i]-ll<s)   //距离是否小于s
		   rr++;  //如果还小说明距离可以再大点试试
		else
		   ll=a[i];  //如果过大了就更新左边界缩小范围
	}
	return rr<=m?1:0;   //避免越界
}
int main()
{
	while(~scanf("%d%d%d",&L,&n,&m))
	{
		for(int i=0;i<n;i++)
	        scanf("%lld",&a[i]);
	    sort(a,a+n);
	    int l=0,r=maxn;
	    a[n]=L;
	    while(l+1<r)   //将距离作为二分法的对象,并利用函数尝试该种最小距离是否可行,直到找到最大的最小距离
	    {
	    	int mid=(l+r)/2;
	    	if(check(mid))  l=mid;
	    	else  r=mid;
		}
		printf("%d\n",l);
	}
 }
4、POJ 2785
  • 题意: 求和问题可以表示为:给定四个整数值列表 A , B , C , D A,B,C,D ABCD,计算出有多少个四元 ( A , B , C , D ) ∈ A × B × C × D (A,B,C,D)∈A×B×C×D ABCDA×B×C×D,使得 A + B + C + D = 0 A+B+C+D=0 A+B+C+D=0。在下面,我们假设所有列表都具有相同的大小n。
  • 输入: 输入文件的第一行包含列表n的大小(该值可以高达4000)。然后我们有n行包含四个整数值(绝对值为2^28 ),分别属于A、B、C和D。
  • 输出: 对于每个输入列表,输出和为零的数字四元组组数。
  • 思路: 这题一开始看翻译没读懂题目,以为是所有数里面抽取四个,后来是csdn上看了别人的博客才发现,它的意思是总共四列数字,每列n个,从每列里面抽取一个数字,如果四个数和为0则组数++。然后又开始琢磨这个有四组,要怎么用二分法,当然菜鸡琢磨不出来还是搜了题解,看完直呼nb。就是总共abcd四组,把a和b分为同一组,枚举出所有可能的和值,并存入数组sum1,把c和d分为同一组,也枚举出所有可能的和值并存入数组sum2,最终对sum1和sum2这两个对象用二分算法来做,就又回到了模板流程。
#include<algorithm>
#include<iostream>
#include<stdio.h>
using namespace std;
const int maxn=16000005;
int n,a[4010],b[4010],c[4010],d[4010],sum1[maxn],sum2[maxn],l,r,len;
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		for(int i=0;i<n;i++)
			scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
		len=0;
		for(int i=0;i<n;i++)   //将a、b归为同一组计算所有和值存入sum1
		{
			for(int j=0;j<n;j++)
			{
				sum1[len]=a[i]+b[j];
				len++;
			}
		}
		len=0;
		for(int i=0;i<n;i++)   //将c、d归为同一组计算所有和值存入sum2
		{
			for(int j=0;j<n;j++)
			{
				sum2[len]=c[i]+d[j];
				len++;
			}
		}
		int ans=0;
		sort(sum2,sum2+len);
		for(int i=0;i<len;i++)
		{
			l=0,r=len-1;   
			while(l<r)    //二分模板
	       {
	    	   int mid=(l+r)/2;
	    	   if(sum1[i]+sum2[mid]<0)  l=mid+1;
	    	   else  r=mid;
		   }
		   while(sum1[i]==-sum2[l]&&l<len)
		   {
		   	ans++;
		   	l++;
		   }
		}
		printf("%d\n",ans);
	}
    return 0;
 }

ps:对于模板中while循环的条件以及l和r的初始值一般不同题目都是+1、-1的区别,具体条件是什么可以拿临界值自己去模拟一遍

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值