洛谷二分法题单

预备知识:

本文默认读者学过最基本的二分。

本文将层层深入讲解二分变式

首先是两个二分法的简单变式(数组从小到大排序)

// 1. 若在有序数组中有多个 value,这个函数会返回第一个的索引
// 2. 找大于(数组中不存在 value)等于 value(存在)的位置
int lowerBound(int left, int right, int value) {
    while (left < right) {
        int mid = (left + right) >> 1;
        if (grade[mid] < value) {  // 如果 mid 位置的值小于 value,继续向右找
            left = mid + 1;
        } else {  // 否则,向左继续找
            right = mid;
        }
    }
    return left;  // 返回第一个大于等于 value 的位置
}

// 1. 若在有序数组中有多个 value,这个函数会返回最后一个的索引
// 2. 找小于(数组中不存在 value)等于 value(存在)的位置
int upperBound(int left, int right, int value) {
    while (left < right) {
        int mid = (left + right) >> 1;
        if (grade[mid] <= value) {  // 如果 mid 位置的值小于等于 value,继续向右找
            left = mid + 1;
        } else {  // 否则,继续向左找
            right = mid;
        }
    }
    return left - 1;  // 返回最后一个小于等于 value 的位置
}

这两函数是根据作用1来命名的。这两个函数在执行作用2时,若是没有和value相等的元素,会一直进行到left==right,即区间长度为1,大家可以自己模拟一下。

第一题:

不同于一般的二分法,本题有可能查不到元素,随便使用上面两个函数的一个就行,如果最后找到的数字不是待查询的数字,输出-1即可。

#include<bits/stdc++.h>
using namespace std;
#define MAX 1000006
int num[MAX];
int likeBinarySearch(int num[], int ask, int size);

int main(void){
    int n, m;
    cin >> n >> m;

    for(int i = 0; i < n; i++){
        cin >> num[i];
    }

    for(int i = 0; i < m; i++){
        int ask;
        cin >> ask;

        int res = likeBinarySearch(num, ask, n);
        cout << res << " ";
    }

    return 0;
}

int likeBinarySearch(int num[], int ask, int size){
    int left = 0, right = size;
    while(left < right){
        int mid = left + (right - left) / 2; // 正确的 mid 计算方式
        if(num[mid] < ask){
            left = mid + 1; // 如果 mid 小于 ask,left 需要加 1
        } else {
            right = mid; // 如果 mid 大于或等于 ask,继续在左半部分查找
        }
    }
    if(num[left] == ask)
    	return left + 1;
    return -1;
}

第二题:

首先提醒大家注意数据范围,要用long long的

转换成A=B+C的个数,转换成对于每个B都要找有没有满足的A,只能时扫描一遍数组,对于每个B(num[i]),在i+1到n二分查找A,因为二分首先要排序,A肯定大于B,所以只能出现在B的右边啦。由于可能会有多个值符合要求,所以利用lowerBound找到最左边的,upperBound找到最右边的,相减即可。当然也可以找到一边之后往右或往左进行遍历,直到不满足:A=B+C

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 200005
ll num[N];

// 找到目标值的最左侧(第一次出现的索引)
int lowerBound(int value, int left, int right) 
{
    while (left < right) {
        int mid = (left + right) >> 1;
        if (num[mid] < value) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

// 找到目标值的最右侧(最后一次出现的索引+1)这里没有return left-1是因为正好这个函数的结果
//减去lowerBound的结果就是区间长度 
int upperBound(int value, int left, int right) {
    while (left < right) {
        int mid = (left + right) >> 1;
        if (num[mid] <= value) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

int main(void) {
    int n;
    ll c;
    cin >> n >> c;

    for (int i = 0; i < n; i++) {
        cin >> num[i];
    }

    sort(num, num + n);  // 对数组进行排序

    ll ans = 0;  // 初始化答案
    for (int i = 0; i < n; i++) {
        // num[i] 是 B,找 A = B + C
        ll value = num[i] + c;
        
        // 使用 lowerBound 找到第一个等于 value 的位置
        int lower = lowerBound(value, i + 1, n);
        
        // 使用 upperBound 找到第一个大于 value 的位置
        int upper = upperBound(value, i + 1, n);
        
        // 如果存在目标值,则增加答案
        if (lower < upper) {
            ans += upper - lower;  // 计算 value 的出现次数
        }
    }

    cout << ans << endl;  // 输出答案
}

第三题:

在binarySearch中有

int mid = (left + right) >> 1;
        if (num[mid] < value) {
            left = mid + 1;
      ....

出题人在一些题目中会把if条件判断的内容变得更加复杂。

首先我们要明确二分对象,就是待输出的锯片最大高度,假设有一个函数func(height),能判断高度height是否满足要求。我们只需要把前面代码中的if (num[mid] < value)换成if (func(height))即可。还要注意,如果这个高度满足要求,我们需要的是继续二分查找比这个高度更高的位置行不行,所以left = mid + 1。其实就是相当于把二分查找中的if的条件变换成一个判断函数而已。

(额,这道题用普通二分查找的模板,单独讨论等于的情况也可以,等于就直接终止二分查找。我这里其实等于的话也进行left = mid +1,(假设我们把这个时候的mid+1称为temp),继续二分查找右边,但是可以预见右边是根本查不到的,所以直接是导致left和right都指向temp,temp是刚好不满足的第一个元素,所以return的是temp-1,也就是代码中的left-1。如果没有等于的元素最终left和right也是会指向刚好不满足的第一个元素,所以return left - 1也没问题。

(咳咳,请大家好好理解

#include<bits/stdc++.h>
using namespace std;

#define N 1000007
bool func(int mid);
int bianrySearch(int left,int right); 
typedef long long ll;
int tree[N];
int n,m;
int main(void){
	
	cin >> n >>m;//m表示需要的总木材数 
	
	for(int i = 0;i < n;i ++){
		
		cin >> tree[i];
	}
	
	sort(tree,tree + n);
	
	int left = 0,right = tree[n-1];
	
	
	cout << binarySearch(left,right) << endl;
}
int binarySearch(int left,int right){//返回第一个大于等于该元素的位置 
	while(left < right){
		int mid = (left + right) >> 1;
		
		if(func(mid)){
			left = mid + 1;
		}
		else{
			right = mid;
		}
	}
	return left - 1;
}
bool func(int mid){
	ll ans = 0;
		for(int i = 0;i < n;i ++){
			if(tree[i] > mid){
				ans += tree[i] - mid;
			}
		}
		if(ans >= m)return true;
		return false;
}

(还有几道题,等我刷完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值