二分查找

ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443

出自蓝桥学苑 GtDzx老师
#二分查找(1)
首先我们来看一个最简单的二分查找算法:
假设数组a[0], a[1], … a[n-1]是单调递增的,并且没有重复的数值。要求在a数组中查找x,如果x存在,返回对应的下标;否则输出-1。

#include 
using namespace std;
int n, x, a[100010];
int binary_search(int* a, int n, int x){ // 闭区间
	int ans = -1;
	int l=0, r=n-1;
	while(l <= r){
		int m = l + ((r-l)>>1);
		if(a[m]==x){
			ans = m;
			break;
		}
		if(a[m]<x){
			l=m+1;
		}else{
			r=m-1;
		}
	}
	return ans;
}
int main(){
	cin >> n >> x;
	for(int i=0; i<n; i++) cin >> a[i];
	int ans = binary_search(a, n, x);
	cout << ans << endl;
	return 0;
}

#二分查找(2)

C++ STL提供了两个特别好用的函数:lower_bound()和uppper_bound()。假设a是一个数组,n是数组长度,lower_bound(a, a+n, x)返回数组a[0]~a[n-1]中,大于等于x的数中,最小的数的指针。由于指针可以通过加减算偏移量,所以我们再减去a(数组名会被隐式转换成指针),就得到了相应的下标。我们看一下下面的例子,假设我们在a数组中找3这个数。
二分查找_第1张图片
>>另一个函数叫做upper_bound()。同样假设a是一个数组,n是数组长度,upper_bound(a, a+n, x)返回数组a[0]~a[n-1]中,大于x的数中,最小的数的指针。注意lower_bound是“大于等于”,upper_bound是“大于”。我们看一下下面的例子,假设我们在a数组中找3这个数。
二分查找_第2张图片

举例:

#include 
#include 
using namespace std;
int main(){
	int a[10] = {1,2,2,3,3,3,4,4,4,4};
	int n = 10;
	for(int i=0; i<=5; i++){
		int* lower = lower_bound(a, a+n, i);
		int* upper = upper_bound(a, a+n, i);
		cout << lower-a << " " << upper-a << endl;
	}
/*
0 0
0 1
1 3
3 6
6 10
10 10
*/

另外lower_bound和upper_bound的前两个参数是其实是待查范围的首尾指针(左闭右开区间,不包括尾指针),所以也可以写别的参数。比如下面的程序就是从a[1]开始查找:

#include 
#include 
using namespace std;
int main(){
	int a[10] = {1,2,2,3,3,3,4,4,4,4};
	int n = 10;
	for(int i=0; i<=5; i++){
		int* lower = lower_bound(a+1, a+n, i);
		int* upper = upper_bound(a+1, a+n, i);
		cout << lower-a << " " << upper-a << endl;
	}
}
/*
1 1
1 1
1 3
3 6
6 10
10 10
*/

lower_bound和upper_bound除了能用在数组上,还可以用在vector上。我们知道vector就相当于一个可以长度可变的数组。当用于vector上时,需要注意前两个参数必须是vector的迭代器,同时函数的返回值也是迭代器:

#include 
#include 
using namespace std;
int main(){
	int a[10] = {1,2,2,3,3,3,4,4,4,4};
	vector<int> b(a, a+10);
	int n = 10;
	for(int i=0; i<=5; i++){
		vector<int>::iterator lower = lower_bound(b.begin(), b.end(), i);
		vector<int>::iterator upper = upper_bound(b.begin(), b.end(), i);
		cout << lower-b.begin() << " " << upper-b.begin() << endl;
	}
	return 0;
}
/*
0 0
0 1
1 3
3 6
6 10
10 10
*/

注意my_lower_bound和C++STL自带的lower_bound有区别,my_lower_bound的第一个参数是一个数组,第二个参数是数组长度,第三个参数是待查找的值;它的返回值是“大于等于x的数中,最小的数的下标“,另一个等价的说法是“假设在a数组中插入x,并保持插入后仍然有序,最小的合法插入下标”。实现:

#include 
using namespace std;
int n, x, a[100010];
int my_lower_bound(int a[], int n, int x){
	int ans = n;
	int l=0, r=n-1;
	while(l<=r){
		int m = l + ((r-l)>>1);
		if(a[m]>=x){
			ans = m;
			r = m-1;
		}else{
			l = m+1;
		}
	}
	return ans;
}
int main(){
	cin >> n >> x;
	for(int i=0; i<n; i++) cin >> a[i];
	int lower = my_lower_bound(a, n, x);
	cout << lower << endl;
	return 0;
}

以上是my_lower_bound的代码,如果我们要实现my_upper_bound的代码,其实也很容易。my_lower_bound是找“大于等于x的数中,最小的数的下标“,所以是if(a[m] >= x)。
my_upper_bound是找“大于x的数中,最小的数的下标“,所以只需要把f(a[m] >= x)改成if(a[m]>x)即可。

#include 
using namespace std;
int n, x, a[100010];
int my_upper_bound(int a[], int n, int x){
	int ans = n;
	int l=0, r=n-1;
	while(l<=r){
		int m = l + ((r-l)>>1);
		if(a[m]>x){
			ans = m;
			r = m-1;
		}else{
			l = m+1;
		}
	}
	return ans;
}
int main(){
	cin >> n >> x;
	for(int i=0; i<n; i++) cin >> a[i];
	int lower = my_upper_bound(a, n, x);
	cout << lower << endl;
	return 0;
}

#二分查找(3)
例题:分巧克力:http://lx.lanqiao.cn/problem.page?gpid=T441

/*分巧克力*/
#include
#define ll long long 
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int maxn=1e5;
int n,k,w[maxn+5],h[maxn+5];
// 判断时候能分配成功 
bool ok(int a){
	ll ret=0,num1,num2;
	fo(i,1,n){
		num1 = w[i]/a; // 宽能分几块 
		num2 = h[i]/a; // 长能分几块 
		ret += num1*num2; // 该巧克力能分为几块边长为a的巧克力 
		if(ret>=k)return true;
	}
	return ret>=k;
}
// 二分过程 
void solve(){
	int l=1,r=100001,mid, ans=1; // l,r 为二分上下界 
	while(l<=r){
		mid = l + ((r-l)>>1); // 取中间值 
		if(ok(mid)){
			ans = mid;
			l = mid+1;
		}else{
			r = mid-1;
		}
	}
	printf("%d\n",ans);
}
int main() {
	scanf("%d%d",&n,&k);
	fo(i,1,n){
		scanf("%d%d",&w[i],&h[i]);
	}
	solve();
	return 0;
}

#1357 : 小Ho的防护盾 http://hihocoder.com/problemset/problem/1357
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
小Ho的虚拟城市正在遭受小Hi的攻击,小Hi用来攻击小Ho城市的武器是一艘歼星舰,这艘歼星舰会以T(T为大于0的整数)个单位时间的间隔向小Ho的城市轰击。歼星舰总共有N枚炮弹,其中第i枚会造成Ai点伤害值。

幸好小Ho的城市有K层护盾,每层护盾可以抵挡M点伤害。当某次轰击使得伤害值达或超过M时,该层护盾就会被击碎;该次轰击溢出的伤害不会作用于下一层护盾;下一次轰击将由下一层护盾承受。

同时,受损但尚未被击碎护盾会以每单位时间减少1点伤害值的速度修复自己,直到伤害值降为0。这就意味着小Hi的攻击间隔T越大,小Ho撑过这N枚炮弹的可能性就越大。

那么问题来了,小Hi的攻击间隔T至少需要是多少,小Ho城市的防护护盾才能不被全部击破?

为了使题目不存在歧义,规定:

小Hi的第i次攻击发生在时刻(i-1)*T

小Ho的第i次修复发生在时刻i-0.5

输入
第一行包含3个整数N、M和K,意义如前文所述。

第二行包含N个整数A1 - AN,表示小Hi第i枚炮弹的伤害值。

对于30%的数据,满足N<=100

对于100%的数据,满足1<=N<=100000

对于100%的数据,满足1<=K<=10, 1<=Ai, M<=100000

输出
输出使得小Ho城市的防护护盾不被全部击破的小Hi攻击间隔的最小值。如果不存在这样的T,则输出-1。

样例输入
3 5 1
3 3 3
样例输出
3

#include
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int maxn=1e5;
int n,m,k,a[maxn+5]; 
bool ok(int T){ 
	int res = k, now=m;
	fo(i,1,n){
		if(a[i]>=now){ // 破坏掉了,换下一个护盾 
			now = m;
			res--;
			if(res<=0)return false;
		}else{
			now = min(m, now-a[i]+T);
		}
	}
	return res>0;
}
// 有解情况 
void solve(){
	// 间隔最大为m,间隔最小为1!!! 二分的区间一定要认真审阅,往往这就是隐藏的错误,不易发现 
	//小Hi的第i次攻击发生在时刻(i-1)*T 所以二分区间不包含0 (题目也规定了T大于0)
	// 小Ho的第i次修复发生在时刻i-0.5 
	int l=1,r=m,mid,ans=m; 
	while(l<=r){
		mid = l + ((r-l)>>1);
		if(ok(mid)){
			ans = mid;
			r = mid-1;
		}else{
			l = mid+1;
		}
	}
	printf("%d\n",ans);
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	int ret = 0;
	fo(i,1,n){
		scanf("%d",&a[i]);
		if(a[i]>=m)ret++;
	}
	// 超过k枚导弹能直接击穿护盾 
	if(ret>=k){
		puts("-1");
		return 0;
	}
	solve();
	return 0;	
}

你可能感兴趣的:(二分查找,ACM算法日常)