二分 模板

二分简介:在一个有序的数组中,如果我们要查询一个数在数组中的下标,我们会对数组进行遍历,进行暴力的搜索,时间复杂度为O(n),我们可以使用二分法,时间复杂度为O(log n)。进行二分的前提是数组具有单调性

暴力程序:

int a[10] = {1,2,3,4,5,6,7,8,9};
int ex_search(int x)
{
	for(int i=0;i<=9;i++){
		if(a[i]==x) return i;
	}
	return -1;
}

下面是一个普通的二分查找函数:

int binarySearch(const std::vector<int>& arr, int target) {
    int left = 0,right = arr.size() - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) return mid;
		else if (arr[mid] < target) left = mid + 1;
		else right = mid - 1;
    }
    return -1;
}

同样你可以使用 binarySearch 函数。

#include <bits/stdc++.h>

using namespace std;

int main() {
    std::vector<int> arr = {1, 3, 5, 7, 9, 11};
    int target = 7;
    int result = binarySearch(arr, target);
    if (result != -1) cout << "Found at index: " << result << endl;
    else cout << "Not found" << endl;
    return 0;
}

damn是这个函数太低级了,我们如果想要知道大于等于X的第一个数,或者小于等于X的最后一个数,就无法实现了。

下面是两个模板:

#include <bits/stdc++.h>

using namespace std;
int a[11] = {0,1,2,3,4,4,5,6,7,8,9};
int ex_search(int x)
{
	for(int i=0;i<=9;i++){
		if(a[i]==x) return i;
	}
	return -1;
}
int Bsearch_left(int l,int r,int x)
{
	while(l<r){
		int mid = l+r>>1;
		if(a[mid]>=x) r = mid;
		else l = mid+1;
	}
	return r;
}
int Bsearch_right(int l,int r,int x)
{
	while(l<r){
		int mid = l+r+1>>1;
		if(a[mid]<=x) l = mid;
		else r = mid-1;
	}
	return r;
}
int main()
{
	cout << Bsearch_left(1,10,4) << '\n';
	cout << Bsearch_right(1,10,4);
	return 0;
}

分别输出:4 5,对应第一个4和第二个4.

相信作为大佬的你意识到这既是upper_bound和lower_bound两个函数

我们既可以改动代码

#include <bits/stdc++.h>

using namespace std;

int a[11] = {0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9};

int ex_search(int x) {
    for (int i = 0; i <= 9; i++) {
        if (a[i] == x) return i;
    }
    return -1;
}

int main() {
    int l = 1, r = 10, x = 4;
    
    // 使用 lower_bound 替代 Bsearch_left
    auto it_left = lower_bound(a + l, a + r + 1, x);
    int left_index = (it_left != a + r + 1) ? (it_left - a) : -1;
    cout << left_index << '\n';

    // 使用 upper_bound 替代 Bsearch_right
    auto it_right = upper_bound(a + l, a + r + 1, x);
    int right_index = (it_right != a + l) ? (it_right - a - 1) : -1;
    cout << right_index << '\n';

    return 0;
}

下面有两道简单的例题:

【深基13.例1】查找 - 洛谷

A-B 数对 - 洛谷

介于我们的函数无论是否查找到,都会返回一个数值,我们需进行检查。

下面是题1的解:

#include <bits/stdc++.h>

using namespace std;

const int N =1e6+5;
int a[N];
int n,m;
int bsearch(int x)
{
	int l=1,r=n;
	while(l<r){
		int mid = l+((r-l)>>1);
		if(a[mid]>=x) r = mid;
		else l = mid + 1;
	}
	return r;
}
int main()
{
	cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> a[i];
	while(m--){
		int t;
		cin >> t;
		int f = bsearch(t);
		if(a[f]==t) cout << f << ' ';
		else cout << -1 << ' ';
	}
	return 0;
 } 

下面是题2的解法1(不使用二分):

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
const int N = 2e5+5;
map<ll,ll> m;
long long k[N];
int n,c,tot = 0;
ll ans = 0;
int main()
{
	cin >> n >> c;
	for(int i=1;i<=n;i++){
		ll t;
		cin >> t;
		if(m[t]==0) k[++tot] = t;
		m[t]++;
	}
	sort(k+1,k+tot+1);//可以不加
	for(int i=1;i<=tot;i++){
		ll a = k[i]+c;
		ans+= m[a]*m[k[i]];
		
	}
	cout << ans;
	return 0;
 } 

主要的优化就在于使用桶的计数,解决了重复匹配的问题

解法二(使用二分):

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
const int N = 2e5+5;

long long k[N];
int n;
ll c,ans = 0;
int bsearch_left(int x)
{
	int l =1,r = n;
	while(l<r){
		int mid = l+r>>1;
		if(k[mid]>=x) r = mid;
		else l = mid+1;
	}
	return r;
}
int bsearch_right(int x)
{
	int l = 1,r = n;
	while(l<r){
		int mid = l+r+1>>1;
		if(k[mid]<=x) l = mid;
		else r = mid-1; 
	}
	return r;
}
int main()
{
	
	cin >> n >> c;
	for(int i=1;i<=n;i++) cin >> k[i];
	sort(k+1,k+n+1);
	for(int i=1;i<=n;i++){
		ll a = k[i]+c;
		if(k[bsearch_left(a)] == a && k[bsearch_right(a)] == a){
			ans += (bsearch_right(a)-bsearch_left(a)+1);
		}
	}
	cout << ans;
	return 0;
 } 

可以看一下大佬的博客:

upper_bound和lower_bound用法(史上最全)_upperbound用法-CSDN博客

二分查找 & 二分答案 万字详解,超多例题,带你学透二分。_c++二分答案怎么确定是l<r还是l<=r-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值