算法模版:基础算法之二分法【沈七】

算法模版:基础算法之二分法

前言

唤我沈七就好啦。
有人戏言90%的程序员都不能一次写对二分的程序。
这句话在学习二分的近一个月里,我真的是深有体会。
下面就来分享一下我这一个月的血泪经验。
还是那句话。
蒟蒻因为实在是太弱了,肯定免不了错误百出。
欢迎批评指正,期待共同成长!

二分法

整数二分

二分的本质是边界。

假设在一个区间上定义了某种性质,整个区间可以被一分为二,

使得这个性质在右半段区间满足而在左半段不满足。

二分可以寻找边界,既可以找到左半段的右边界a,也可以找到右半段的左边界b

l ab r
xxxxxxxxxoooooooooooooooooooo

找a

情况一:即寻找符合性质的第一个点

区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:

int bsearch_1(int l, int r)
{
    
    
    while (l < r)
    {
    
    
        int mid = l + r >> 1;// >> 右移操作符,相等于 (l+r)/2 但>>的速度比 / 效率高的多
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}

找b

情况二:寻找符合性质的最后一个点

区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:

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;
}

问:这里 为什么是 mid = l + r + 1 >>1?

答:在这种情况中,如果mid = l + r>> 1 , 会陷入死循环。

所以只要牢记:

当 l = mid 时 ,mid = l + r + 1 >>1

当 r = mid 时 ,mid = l + r >>1

数的范围

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。

对于每个查询,返回一个元素 k的起始位置和终止位置(位置从 0 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 nq,表示数组长度和询问个数。

第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。

接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式

q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

1≤n≤100000
1≤q≤10000
1≤k≤10000

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1

l a b r
1 2 2 3 3 3 4

求a

1.找一个判断条件,使得该判断条件使区间具有二段性,且答案一定是在该二段性的边界.

a是区间内大于等于x的分界,a是大于等于x的第一个数。

(a之前的数值均小于x)
2.分析中点mid在该判断条件下是否成立.

check(mid)>=x?r=mid:l=mid+1;

3.更新方式是r=mid,不做任何处理。mid=l=r>>1;

求b

1.找一个判断条件,使得该判断条件使区间具有二段性,且答案一定是在该二段性的边界.

b是区间内小于等于x的分界

(b之后的数值均大于x)
2.分析中点mid在该判断条件下是否成立.

check(mid)<=x?l=mid:r=mid-1;

3.更新方式是l=mid,mid需要加1,mid=l+r+1>>1.

#include<bits/stdc++.h>
using namespace std;
const int  N = 1e5+10;
int s[N];
int main()
{
    
    
	int n,m;
	cin>>n>>m;
	for(int i = 0 ; i < n ; i ++)
	cin>>s[i];
	while(m--)
	{
    
    
		int x;cin>>x; 
		int l = 0, r = n -1 ;
		while(l<r)
		{
    
    
			int mid = l+r>>1;
			if(s[mid]>=x)	
			r=mid;
			else
			l=mid+1;
		}
		if(s[l]==x)
		cout<<l<<" ";
		else
		cout<<"-1 ";
		r = n - 1 ;
		while(l< r)
		{
    
    
			int mid = l + r + 1>>1;
			if(s[mid]<= x)
			l=mid;
			else
			r=mid-1;
		}
		if(s[l]==x)
		cout<<l<<"\n";
		else
		cout<<"-1\n";
	}
	return 0;
} 

小数二分

bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
    const double eps = 1e-8;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

问:为什么 i=mid 而不是 mid+1 的原因 ?

答:精度原因,答案有可能是 mid + 0.1

剪绳子

N 根绳子,第 i 根绳子长度为 L i,现在需要 M 根等长的绳子,你可以对 N 根绳子进行任意裁剪(不能拼接),请你帮忙计算出这 M 根绳子最长的长度是多少。

题解部分

枚举 长度 x,判断是否能剪出 m 条 长度为 x 。

然后结合 二分的思想,优化枚举。

#include<bits/stdc++.h>
using namespace std;
const int  N = 1e5+10;
double a[N];
int n,m;
bool check(double x)
{
    
    
	int sum=0;
	for(int i =1 ; i <= n ; i ++)
	sum+=a[i]/x;
	return sum>=m;
}
int main()
{
    
    
    cin>>n>>m;
    for(int i = 1 ; i <= n ; i ++)
	cin>>a[i];
	double l=1,r=1e9;
	while(r-l>1e-3)
	{
    
    
		double mid = (l+r)/2;	
		if(check(mid))
		l=mid;
		else
		r=mid; 
	}
	printf("%.2f",l);
	return 0;
}

完结散花

ok以上就是对 基础算法之二分法 的全部讲解啦,很感谢你能看到这儿。如果有遗漏、错误或者有更加通俗易懂的讲解,欢迎小伙伴私信我,我后期再补充完善。

参考文献

https://www.acwing.com/activity/content/19/

猜你喜欢

转载自blog.csdn.net/m0_66139206/article/details/122749144