第二周:二分查找+二分答案

一.二分查找

首先,话不多说先上模板

1.模板一(查某个数第一次出现的位置)

	while (l < r)
    {
        int mid = (l+r)/2;
        if (check(mid))  r = mid;    // check()判断mid是否满足性质,满足则返回true
        else l = mid + 1;
    }

2.模板二(查某个数最后一次出现的位置)

	while (l < r)
    {
        int mid = (l+r+1)/2;
        if (check(mid))  l = mid;
        else r = mid - 1;
    }

注释:两个模板的关键都在4~6行,即如何划分区间。模板一会将区间划为[l, mid]和[mid+1, r][l, mid]和[mid+1, r],而模板二会将区间划为[l, mid−1]和[mid, r][l, mid−1]和[mid, r]。这就是模板最重点的区别。

​ 模板上实际也是可以互换的

  • 两个模板最后返回时,都有l==rl==r,且l、rl、r都等于划分的第一个区间的右端点,即mid或mid - 1。

  • 模板二求mid时需要+1,这是为了防止死循环。

3.模板三(浮点数)

	while(r-l>1e-5) //需要一个精度保证
	{
		double mid = (l+r)/2;
		if(check(mid)) l=mid; //或r=mid;
		else r=mid; //或l=mid;
	}

然后,来一道简单的模板题练练手P2249 【深基13.例1】查找 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 首先,用stl中的lower_bound秒杀(内核还是二分法)

#include<bits/stdc++.h>
using namespace std;
int a[1000000];
int main() {
	int n, m; cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	for (int i = 1; i <= m; i++) {
		int x;	cin >> x;
		int ans = lower_bound(a + 1, a + n + 1, x) - a;//如果找不到则会找到大于它的第一位数
		if (a[ans] == x)cout << ans << " ";//判断是否是正确位置
		else cout << "-1 ";
	}
	return 0;
}

二分模板解题

#include<bits/stdc++.h>
using namespace std;
int a[100005];
int main() {
	int n, m; cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	for (int i = 1; i <= m; i++) {
		int pos; cin >> pos;
		int left = 1, right = n;
			while (left < right)
			{
				int mid = (left + right) / 2;
				//要求找第一个数字则往前面寻找是否还有,如果没有的话最终循环还是在最初的mid下结束
				if (a[mid] >= pos)right = mid;
				else left = mid + 1;
			}
			if (a[left] == pos)cout <<left << " ";//最后left和mid相等
			else cout << "-1 ";
		}
	return 0;
}

因为要求找第一次出现的位置则用模板一,,同时我们也可以举一反三,将代码修改一下就可以找最后一次出现的位置,代码如下

#include<bits/stdc++.h>
using namespace std;
int a[100005];
int main() {
	int n, m; cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	for (int i = 1; i <= m; i++) {
		int pos; cin >> pos;
		int left = 1, right = n;
		while (left < right)
		{
			int mid = (left + right+1) / 2;
			if (a[mid] <= pos)left=mid;//注意对比俩者区别
			else right=mid-1;
		}
		if (a[left] == pos)cout << left << " ";
		else cout << "-1 ";
	}
	return 0;
}

相信现在已经渐入佳境了,那再来一道浮点数的二分法,以及用模板如何解答

 

#include<bits/stdc++.h>
using namespace std;
double sum, back, mon;//back表示每月还多少
double M;
bool check(double mid) {
	 M = sum;
	for (int i = 1; i <= mon; i++) {
		M = M + M * mid - back;
	}
	if (M > 0)return true;//如果大于0,表示还不完,则利率需要减少
	else return false;
}
int main() {
	cin >> sum >> back >> mon;
	double left = 0, right = 100;//right可以尽可能赋值大一点
	while (right-left>1e-5)
	{
		 double mid = (left + right) / 2;
		if (check(mid))right = mid;//利率将要减少
		else left = mid;
	}
	cout << fixed<<setprecision(1) << left * 100 << endl;
	return 0;
}

二.二分答案

这种类型的题一般都会让你求最大最近距离和最小最大距离,有点抽象哈,那么先上万能模板再来俩道题练练手就懂了。

模板

	while (left <= right)//进行答案二分
	{
		mid = (left + right) / 2;//假设mid满足则返回true后向右边遍历,反之向左边
		if (check(mid)) {
			left = mid + 1;
		}
		else right = mid - 1;
	}

P1824 进击的奶牛 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 

#include<bits/stdc++.h>
using namespace std;
int n, c, ans;
int a[1000005];
bool check(int mid) {
	int now = 1, count = 1;//now表示目前的位置,count表示有几个位置可以满足,且注意起始个数,至少有一个
	for (int i = 2; i <= n; i++) {
		if (a[i] - a[now] >= mid) {//表示俩者之间的最大距离大于或等于给定答案值则符合
			now = i;
			count++;
		}
	}
	return count >= c;//如果count大于c(牛的个数)说明此方案可行,返回true
}
int main() {
	ans = -1;
	cin >> n >> c;//n表示栅栏总数,c表示牛的个数
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	sort(a + 1, a + 1 + n);
	int left = 1, right = 10000000, mid;
	while (left <= right)//进行答案二分
	{
		mid = (left + right) / 2;//假设mid满足则返回true后向右边遍历,反之向左边
		if (check(mid)) {
			left = mid + 1;
			ans = max(ans, mid);
		}
		else right = mid - 1;
	}
	cout << ans << endl;
	return 0;
}

P2678 [NOIP2015 提高组] 跳石头 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 

#include<bits/stdc++.h>
using namespace std;
int a[2000000];
int L, N, M;

bool check(int mid) {
	int now = 0, count = 0;//一开始在岸上所以初始是没有石头记录,但是注意这里会把最后一个岸算作石头
//所以后面判断条件为count>=x+1;
	int x = N - M;//表示余下的石头
	for (int i = 1; i <= N + 1; i++) {
		if (a[i] - a[now] >= mid) {
			now = i;
			count++;
			if (count >= x + 1)return true;//表示该方案可行
		}
	}
	return false;
}
int main() {
	cin >> L >> N >> M;
	a[N + 1] = L;
	for (int i = 1; i <= N; i++) {
		cin >> a[i];
	}
	int left = 0, right = L,mid,ans=-1;
	while (left<=right)
	{
		mid = (left + right) / 2;
		if (check(mid)) { left = mid + 1; 
		ans = max(ans, mid);
		}
		else right = mid - 1;
	}
	cout << ans << endl;
}

再分享几道题,可以看完文章之后自己练练

P2440 木材加工 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

P3853 [TJOI2007]路标设置 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值