东方 - 二分查找和二分答案

二分

二分查找

使用二分查找的前提条件主要包括以下几点:

1. **有序序列**:二分查找的核心前提是目标序列必须是有序的。这个序列可以是升序或降序排列,但是必须保证整个搜索过程中的顺序一致。如果数据未排序,二分查找无法正确工作。

2. **随机访问迭代器**:二分查找需要能够快速访问序列中任意位置的元素,因此适用于如数组、`std::vector``std::deque` 等支持随机访问迭代器的容器。它不适用于如 `std::list``std::forward_list` 这样只支持顺序访问的容器。

3. **确定的界限**:二分查找需要明确的搜索范围,即序列的起始和结束位置。这通常通过提供两个迭代器来实现,分别指向序列的开始和结束。

4. **合适的比较逻辑**:二分查找通过比较元素来缩小搜索范围。因此,元素之间必须有有效的比较逻辑。默认情况下,使用 `<` 操作符,但你也可以提供自定义的比较函数。

5. **处理重复元素**:如果序列中包含重复元素,二分查找依然有效,但可能需要额外的处理来确定特定元素的确切位置。例如,`std::lower_bound``std::upper_bound` 可以用来在包含重复元素的序列中定位特定值。

6. **足够的性能优势**:对于较小的数据集,二分查找提供的性能优势可能不明显,甚至不如简单的线性搜索。它更适合于中到大型数据集。

遵循这些条件,二分查找可以高效地定位目标元素,将搜索时间从线性时间复杂度降低到对数时间复杂度。

1236 - 二分查找

正常版本

#include <iostream>
using namespace std;

int binarySearch(int nums[], int n, int target) {
    int left = 1, right = n;

    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid; // 找到目标,返回其索引
        } else if (nums[mid] < target) {
            left = mid + 1; // 目标在右半部分
        } else {
            right = mid - 1; // 目标在左半部分
        }
    }

    return -1; // 没找到目标,返回-1
}

int main() {
    int n, x;
    cin >> n;
    int nums[n+1]; // 创建一个大小为n+1的数组,因为我们从索引1开始使用

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

    cin >> x;
    int result = binarySearch(nums, n, x);

    cout << result; // 直接输出结果,因为我们的数组索引已经是从1开始的

    return 0;
}

STL模板库版本

#include <iostream>
#include <vector>
using namespace std;

int binarySearch(const vector<int>& nums, int target) {
	//这里-1是因为n个数下标从0开始排序到n-1
    int left = 0, right = nums.size() - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid; // 找到目标,返回其索引
        } else if (nums[mid] < target) {
            left = mid + 1; // 目标在右半部分
        } else {
            right = mid - 1; // 目标在左半部分
        }
    }

    return -1; // 没找到目标,返回-1
}

int main() {
    int n, x;
    cin >> n;
    vector<int> nums(n+1);

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

    cin >> x;
    int result = binarySearch(nums, x);

    // 输出结果,注意题目要求的位置是从1开始计数的,所以需要加1
    if (result != -1) {
        cout << result;
    } else {
        cout << -1;
    }

    return 0;
}

从1开始的版本

#include <iostream>
#include <vector>
using namespace std;

int binarySearch(const vector<int>& nums, int target) {
	//这里left 和right传的是下标不是元素个数
	//这里的-1是减去.size算的时候加上了nums[0]的那个数
    int left = 1, right = nums.size() - 1; // 从1开始

    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid; // 找到目标,返回其索引
        } else if (nums[mid] < target) {
            left = mid + 1; // 目标在右半部分
        } else {
            right = mid - 1; // 目标在左半部分
        }
    }

    return -1; // 没找到目标,返回-1
}

int main() {
    int n, x;
    cin >> n;
    vector<int> nums(n + 1); // 使用大小为 n+1 的数组,以便从1开始

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

    cin >> x;
    int result = binarySearch(nums, x);

    cout << result; // 直接输出结果,如果未找到,binarySearch返回-1

    return 0;
}

小学生版本
全局变量

#include <iostream>
#include <vector>
using namespace std;
vector<int> nums;
int n,x;
//STL库vector来做3.0
int binarySearch(int t)
{
	int l = 1,r = n,mid;
	
	while(l<=r)
	{
		mid = l + (r - l)/2;
		if(t == nums[mid]){
			return mid;
		}else if(t > nums[mid]){
			l = mid + 1;
		}else{
			r = mid - 1;
		}
	}
	return -1;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	cin>>n;
	nums.resize(n+1,0);
	//括号内的值表示数据量预留的大小 
	//第二个参数传默认的值
	for(int i=1; i<=n; i++){
		cin>>nums[i];
	}
	cin>>x;
	cout<<binarySearch(x);
	
	return 0;
}

局部变量

#include <iostream>
#include <vector>
using namespace std;
//STL库vector来做2.0
int binarySearch(vector<int>& nums,int n,int t)
{
	int l = 1,r = n,mid;
	
	while(l<=r)
	{
		mid = l + (r - l)/2;
		if(t == nums[mid]){
			return mid;
		}else if(t > nums[mid]){
			l = mid + 1;
		}else{
			r = mid - 1;
		}
	}
	return -1;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	int n,x;
	cin>>n;
	vector<int> nums(n+1,0);
	//括号内的值表示数据量预留的大小 
	//第二个参数传默认的值
	for(int i=1; i<=n; i++){
		cin>>nums[i];
	}
	cin>>x;
	cout<<binarySearch(nums,n,x);
	
	return 0;
}

1894. 二分查找左侧边界

(1)有序不递减的数组(数组里面有相等的元素的时候)
(2)找第一个出现的数

正常版本

#include <iostream>
using namespace std;

const int N = 1e5 + 10; // 根据题目要求,数组大小设为10^5 + 10
int nums[N];

// 二分查找,寻找目标值的第一次出现位置
int findFirstOccurrence(int n, int target) {
    int left = 1, right = n, ans = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (target <= nums[mid]) {
            right = mid - 1;
            if (nums[mid] == target) ans = mid;
        } else {
            left = mid + 1;
        }
    }
    return ans; // 已经是从1开始的索引,无需调整
}

int main() {
	
	ios::sync_with_stdio(false);
	cin.tie(NULL);
 	cout.tie(NULL);
 	
    int n, q;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> nums[i];
    }

    cin >> q;
    for (int i = 1; i <= q; ++i) {
        int x;
        cin >> x;
        int result = findFirstOccurrence(n, x);
        cout << result << " ";
    }
    return 0;
}

STL模板库

#include <iostream>
#include <vector>
using namespace std;

vector<int> nums; // 将vector定义为全局变量

// 自定义的二分查找函数,寻找目标值的第一次出现位置
int findFirstOccurrence(int n, int target) {
    int left = 1, right = n, ans = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (target <= nums[mid]) {
            right = mid - 1;
            if (nums[mid] == target) ans = mid;
        } else {
            left = mid + 1;
        }
    }
    return ans; // 已经是从1开始的索引,无需调整
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n, q;
    cin >> n;
    nums.resize(n + 1); // 使用resize调整大小并赋值
    for (int i = 1; i <= n; ++i) {
        cin >> nums[i];
    }

    cin >> q;
    for (int i = 1; i <= q; ++i) {
        int x;
        cin >> x;
        cout << findFirstOccurrence(n, x) << " ";
    }
    return 0;
}

1895. 二分查找右侧边界

#include <iostream>
using namespace std;

const int N = 1e5 + 10; // 根据题目要求,数组大小设为10^5 + 10
int nums[N];

// 二分查找,寻找目标值的最后一次出现位置
int findLastOccurrence(int n, int target) {
    int left = 1, right = n, ans = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (target >= nums[mid]) {
            left = mid + 1;
            if (nums[mid] == target) ans = mid;
        } else {
            right = mid - 1;
        }
    }
    return ans; // 已经是从1开始的索引,无需调整
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    
    int n, q;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> nums[i];
    }

    cin >> q;
    for (int i = 1; i <= q; ++i) {
        int x;
        cin >> x;
        int result = findLastOccurrence(n, x);
        cout << result << " ";
    }
    return 0;
}

STL模板库版本

#include <iostream>
#include <vector>
using namespace std;

const int N = 1e5 + 10;
vector<int> nums(N); // 将vector定义为全局变量

// 自定义的二分查找函数,寻找目标值的第一次出现位置
int findFirstOccurrence(int n, int target) {
    int left = 1, right = n, ans = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (target>=nums[mid]) {
        	left = mid + 1;
            if (nums[mid] == target) ans = mid;
        } else {
            right = mid - 1;
        }
    }
    return ans; // 已经是从1开始的索引,无需调整
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n, q;
    cin >> n;
    nums.resize(n + 1); // 使用resize调整大小并赋值
    for (int i = 1; i <= n; ++i) {
        cin >> nums[i];
    }

    cin >> q;
    for (int i = 1; i <= q; ++i) {
        int x;
        cin >> x;
        int result = findFirstOccurrence(n, x);
        cout << result << " ";
    }
    return 0;
}

1896. 二分查找满足条件的数

#include <iostream>
#include <vector>
using namespace std;

vector<int> nums; // 将数组替换为 vector

int findFirstOccurrence(int n, int target) {
    int left = 1, right = n, ans = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] >= target) {
            ans = mid;
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n, q;
    cin >> n;
    nums.resize(n + 1); // 使用 resize 调整 vector 的大小
    for (int i = 1; i <= n; ++i) {
        cin >> nums[i];
    }

    cin >> q;
    for (int i = 1; i <= q; ++i) {
        int x;
        cin >> x;
        int result = findFirstOccurrence(n, x);
        if (result == -1) {
            cout << "-1"<<" ";
        } else {
            cout << result << " ";
        }
    }
    cout << endl;
    return 0;
}

std::lower_bound 是 C++ 标准库中的一个二分查找函数,用于在已排序的范围内查找第一个不小于(即大于或等于)给定值的元素。这个函数在 <algorithm> 头文件中定义。它是一个模板函数,因此可以用于任何支持随机访问迭代器的容器,如 std::vectorstd::array、甚至原生数组。

基本用法

std::lower_bound(StartIterator, EndIterator, Value)
  • StartIterator: 搜索的起始位置。
  • EndIterator: 搜索的结束位置(不包括此位置)。
  • Value: 要查找的值。

返回值

  • 返回一个指向范围内第一个不小于 Value 的元素的迭代器。如果所有元素都小于 Value,则返回 EndIterator

时间复杂度

  • std::lower_bound 的时间复杂度是 O(log n),其中 n 是 StartIteratorEndIterator 之间的元素个数。

示例

假设有一个已排序的 std::vector<int>,我们要找到第一个大于或等于 4 的元素的位置。

std::vector<int> v = {1, 2, 2, 3, 4, 4, 5, 6};
auto it = std::lower_bound(v.begin(), v.end(), 4);

在这个例子中,it 将指向第一个值为 4 的元素。

注意事项

  1. 已排序的范围std::lower_bound 适用于已排序的序列。如果序列未排序,结果是未定义的。

  2. 等价元素的处理:对于等价元素(即满足不小于查找值的元素),std::lower_bound 返回这些元素的第一个。

  3. std::upper_bound 的区别std::upper_bound 查找的是第一个大于指定值的元素。

  4. 配合 std::sort 使用:通常,如果序列未排序,你会先使用 std::sort 对其进行排序,然后再使用 std::lower_boundstd::upper_bound 进行查找。
    这就是 std::lower_bound 的基本概念。在实际应用中,它是处理有序序列中的查找问题的一个非常有效的工具。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<int> nums;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n, q;
    cin >> n;
    nums.resize(n + 1);  // 将数组大小设置为 n+1,从下标1开始输入
    for (int i = 1; i <= n; ++i) {
        cin >> nums[i];
    }

    cin >> q;
    for (int i = 0; i < q; ++i) {
        int x;
        cin >> x;
        // lower_bound从下标1开始查找
        auto it = lower_bound(nums.begin() + 1, nums.end(), x);

        // 输出位置或者 -1
        if (it == nums.end() || *it < x) {
            cout << "-1 ";
        } else {
            cout << (it - nums.begin()) << " ";  // 不需要加1,因为实际数据从1开始
        }
    }
    cout << endl;

    return 0;
}

这段代码使用了 C++11 引入的一种新的循环结构,称为基于范围的 for 循环(Range-based for loop)。这种循环提供了一种简洁的方式来遍历容器(如 std::vectorstd::list、数组等)中的所有元素。

基于范围的 for 循环

for (auto num : vec) {
    cout << num << " ";
}

在这个例子中,vec 是一个容器,num 是用于遍历 vec 中每个元素的变量。每次迭代,num 会自动被赋予 vec 中的下一个元素的值。

  • auto: 这是一个自动类型推断关键字。它告诉编译器自动推断 num 的类型。在这种情况下,num 的类型将是 vec 中元素的类型。
  • :: 这个符号用于分隔循环变量和被遍历的容器。

传统的 for 循环

与基于范围的 for 循环相比,传统的 for 循环在遍历容器时需要显式地管理索引或迭代器。下面是对应的传统 for 循环:

//size_t是无符号类型 所以定义的数都是大于等于0的
//因为vector的下标是从0开始的所以使用size_t定义是为了防止数组越界
for (size_t i = 0; i < vec.size(); ++i) {
    cout << vec[i] << " ";
}

或者使用迭代器:

for (auto it = vec.begin(); it != vec.end(); ++it) {
    cout << *it << " ";
}

比较

  • 简洁性:基于范围的 for 循环更加简洁,不需要显式地处理索引或迭代器。
  • 类型安全:使用 auto 关键字可以避免类型不匹配的错误。
  • 可读性:基于范围的循环对于读者来说更容易理解,尤其是在遍历容器时。
  • 应用范围:基于范围的循环适用于任何提供了 beginend 成员函数的容器。传统的 for 循环更灵活,适用于更广泛的场景(例如,当需要索引或迭代器进行更复杂的操作时)。

基于范围的 for 循环是现代 C++ 中推荐的遍历容器的方法,因为它简单且减少了错误的可能性。
我将在每个使用 auto 的地方注释其原本的类型。同时,我也会使用 using namespace std 来简化类型和函数的名称。

示例 1: 遍历容器

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<int> vec = {1, 2, 3, 4, 5};

    // 使用 auto 来遍历 vector
    // auto it = vector<int>::iterator it
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    // 基于范围的 for 循环
    // auto num = int
    for (auto num : vec) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

示例 2: 用于函数返回类型

#include <iostream>
#include <vector>

using namespace std;

// auto 用于自动推断函数返回类型
// 实际上是 int
auto getMax(const vector<int>& vec) -> int {
    int max = vec[0];
    for (auto num : vec) { // auto num = int
        if (num > max) {
            max = num;
        }
    }
    return max;
}

int main() {
    vector<int> vec = {1, 2, 3, 4, 5};
    cout << "最大值: " << getMax(vec) << endl;
    return 0;
}

示例 3: 使用 auto 进行类型推断

#include <iostream>
#include <map>
#include <string>

using namespace std;

int main() {
    map<string, int> map = {{"apple", 1}, {"banana", 2}, {"cherry", 3}};

    // 使用 auto 来推断类型
    // auto pair = pair<const string, int>
    for (auto pair : map) {
        cout << pair.first << ": " << pair.second << endl;
    }

    return 0;
}

在这些示例中,auto 被用于自动推断变量的类型。这在代码中是非常有用的,尤其是当类型名称很长或复杂时。通过使用 auto,代码更加简洁,也减少了因类型错误而引发的潜在问题。

为了解决这个问题,我们可以使用 C++ 中的 std::lower_boundstd::upper_bound 函数。这两个函数都在 <algorithm> 头文件中定义,并且都适用于已排序的序列。

  1. std::lower_bound: 返回指向范围内第一个不小于(即大于或等于)给定值的元素的迭代器。
  2. std::upper_bound: 返回指向范围内第一个大于给定值的元素的迭代器。

这两个函数的结合使用可以帮助我们找到特定值在有序数组中出现的起始位置和结束位置。

2078. 起止位置

解题思路:

  1. 对于每次查询:

    • 使用 std::lower_bound 找到年龄 x 第一次出现的位置。
    • 使用 std::upper_bound 找到年龄 x 最后一次出现的位置。(返回值是最后一个元素的下一位 所以要-1)
  2. 需要注意的是,由于这些函数返回的是迭代器,我们需要将它们转换为数组下标。我们可以通过从迭代器中减去数组开始的迭代器来做到这一点。

  3. 如果 std::lower_bound 返回的迭代器指向数组末尾或者指向的元素不等于 x,说明数组中没有 x,应该输出 “-1 -1”。

示例代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int n, q;
    cin >> n >> q;
    vector<int> ages(n);
    for (int i = 0; i < n; ++i) {
        cin >> ages[i];
    }

    while (q--) {
        int x;
        cin >> x;
        // 查找 x 第一次出现的位置
        auto lower = lower_bound(ages.begin(), ages.end(), x);
        // 查找 x 最后一次出现的位置
        auto upper = upper_bound(ages.begin(), ages.end(), x);

        if (lower == ages.end() || *lower != x) {
            cout << "-1 -1\n";
        } else {
            // 输出位置,从1开始计数
            //upper不需要+1的原因是返回找到的那个位置的下一位本身就已经+1了
            cout << (lower - ages.begin() + 1) << " " << (upper - ages.begin()) << "\n";
        }
    }

    return 0;
}

代码解释:

  • lower_boundupper_bound 分别找到第一个不小于 x 和第一个大于 x 的元素的迭代器。
  • 如果 lower 指向的元素不等于 xlowerages.end()(表示没有找到),输出 “-1 -1”。
  • 否则,输出 lowerupper 指向的元素的位置(1-based 索引)。由于 upper_bound 返回的是大于 x 的第一个元素的位置,所以它实际上指向 x 最后一次出现位置的下一个位置。

解答二:

要用自己编写的二分查找函数解决这个问题,我们需要分别实现两个函数:一个用于找到第一个大于或等于给定年龄的同学的位置(类似于 std::lower_bound),另一个用于找到第一个大于给定年龄的同学的位置(类似于 std::upper_bound)。

解题思路:

  1. findFirstOccurrence: 这个函数实现了类似于 std::lower_bound 的功能。它在有序数组中找到第一个大于或等于给定值 x 的元素的位置。

  2. findLastOccurrence: 这个函数实现了类似于 std::upper_bound 的功能。它在有序数组中找到第一个大于给定值 x 的元素的位置。

  3. 对于每个查询,先调用 findFirstOccurrence 找到第一个出现的位置,然后调用 findLastOccurrence 找到最后一个出现的位置。

  4. 如果 findFirstOccurrence 返回的位置对应的元素不是 x,则表示 x 在数组中不存在,输出 “-1 -1”。否则,输出两个函数返回的位置(调整为 1-based 索引)。

示例代码:

#include <iostream>
#include <vector>
using namespace std;

// 寻找第一个大于或等于x的元素位置
int findFirstOccurrence(const vector<int>& arr, int x) {
    int left = 0, right = arr.size() - 1, res = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] >= x) {
            res = mid;
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return res;
}

// 寻找第一个大于x的元素位置
int findLastOccurrence(const vector<int>& arr, int x) {
    int left = 0, right = arr.size() - 1, res = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] <= x) {
            left = mid + 1;
        } else {
            res = mid;
            right = mid - 1;
        }
    }
    return res;
}

int main() {
    int n, q;
    cin >> n >> q;
    vector<int> ages(n);
    for (int i = 0; i < n; ++i) {
        cin >> ages[i];
    }

    while (q--) {
        int x;
        cin >> x;
        int first = findFirstOccurrence(ages, x);
        int last = findLastOccurrence(ages, x);

        if (first == -1 || ages[first] != x) {
            cout << "-1 -1\n";
        } else {
            // 输出1-based索引
            cout << (first + 1) << " " << (last == -1 ? n : last) << "\n";
        }
    }

    return 0;
}

代码解释:

  • findFirstOccurrencefindLastOccurrence 函数分别执行二分查找,寻找特定条件的元素位置。
  • findFirstOccurrence 寻找第一个不小于 x 的元素,findLastOccurrence 寻找第一个大于 x 的元素。
  • 在主函数中,我们对每个查询使用这两个函数,并根据返回的结果输出相应的位置。
  • 注意,由于 findLastOccurrence 返回的是大于 x 的第一个元素的位置,如果返回 -1 表示数组中所有元素都不大于 x,因此最后一个 x 的位置实际上是数组的长度。

1898. 同时出现的数

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<int> firstGroup; // 存储第一组数字,下标从1开始

// 二分查找函数,确定元素是否存在于第一组数字中
bool binarySearch(const vector<int>& arr, int x) {
    int left = 1, right = arr.size() - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == x) {
            return true; // 找到元素
        }
        if (arr[mid] < x) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return false; // 未找到元素
}

int main() {
    int n, m;
    cin >> n >> m;
    
    // 数组下标从1开始,因此需要多分配一个空间
    firstGroup.resize(n + 1);
    
    // 读取第一组数字并排序,从下标1开始
    for (int i = 1; i <= n; ++i) {
        cin >> firstGroup[i];
    }
    sort(firstGroup.begin() + 1, firstGroup.end());

    vector<int> secondGroup(m + 1);  // 第二组数字,下标从1开始
    // 读取第二组数字,从下标1开始
    for (int i = 1; i <= m; ++i) {
        cin >> secondGroup[i];
    }
    // 排序第二组数字
    sort(secondGroup.begin() + 1, secondGroup.end());

    // 输出在第一组数字中找到的第二组数字
    for (int i = 1; i <= m; ++i) {
        if (binarySearch(firstGroup, secondGroup[i])) {
            cout << secondGroup[i] << " ";
        }
    }

    cout << endl;
    return 0;
}

1899. 最满意的方案

1.对学校的分数进行排序。
2.对于每个学生的估分,找到学校分数列表中最接近其估分的分数。由于学校分数是排序的,我们可以使用二分查找来高效地完成这一步。
3.计算每个学生的不满意度(即学生的估分与找到的最接近学校分数之间的差的绝对值)。
4.求出所有学生的不满意度之和。
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;

// 二分查找函数,找到最接近studentScore的分数
int findClosest(const vector<int>& schoolScores, int studentScore) {
    int left = 0, right = schoolScores.size() - 1;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (schoolScores[mid] < studentScore) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    // 检查左侧邻居,看是否有更接近的分数
    if (left > 0 && abs(schoolScores[left] - studentScore) >= abs(schoolScores[left - 1] - studentScore)) {
        return abs(schoolScores[left - 1] - studentScore);
    }
    return abs(schoolScores[left] - studentScore);
}

int main() {
    int m, n;
    cin >> m >> n;
    vector<int> schoolScores(m);
    vector<int> studentScores(n);

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

    // 对学校分数进行排序
    sort(schoolScores.begin(), schoolScores.end());

    long long totalDissatisfaction = 0;
    for (int score : studentScores) {
        totalDissatisfaction += findClosest(schoolScores, score);
    }

    cout << totalDissatisfaction << endl;
    return 0;
}

1542. 小X算排名

#include<bits/stdc++.h>
using namespace std;
int a[100005],b[100005];
bool cmp(int x,int y){
    return x>y;
}
int main(){
    int n;
    //cin>>n;
    scanf("%d",&n); 
    for(int i=0;i<n;i++){
        //cin>>a[i];
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(a,a+n,cmp);
    for(int i=0;i<n;i++){
        //cout<<lower_bound(a,a+n,b[i],cmp)-a+1<<endl;
        printf("%d\n",lower_bound(a,a+n,b[i],cmp)-a+1);
    }
    return 0;
}

STL库版本

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> a, b; 
int main(){
	int n;
	scanf("%d", &n);
	a.resize(n + 1);
	b.resize(n + 1);
	for (int i = 0; i < n; i++){
		scanf("%d", &a[i]);
		b[i] = a[i];
	}
	sort(a.begin(), a.end(), greater<int>());
	for (int i = 0; i < n; i++){
		printf("%d\n", lower_bound(a.begin(), a.end(), b[i], greater<int>()) - a.begin() + 1);
	}
	return 0;
}

二分答案

1908. 伐木工

#include <iostream>
#include <vector>
#include <algorithm> // 用于max函数
using namespace std;

long long woodCollected(const vector<int>& ttall, int h) {
    long long total = 0;
    for (int height : ttall) {
        if (height > h) {
            total += (height - h);
        }
    }
    return total;
}

int main() {
    int n, maxheight = 0;
    long long m;
    cin >> n >> m;
    vector<int> ttall(n);
    for (int i = 0; i < n; i++) {
        cin >> ttall[i];
        maxheight = max(maxheight, ttall[i]); // 正确的最大高度计算
    }

    int l = 0, r = maxheight;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        long long collected = woodCollected(ttall, mid);
        if (collected < m) {
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    cout << r;
    return 0;
}

1909. 跳石头

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool check(const vector<int>& stones, int maxDist, int M, int L) {
    int lastPos = 0;
    int removed = 0;

    for (int stone : stones) {
        if (stone - lastPos < maxDist) {
            // 移除这块石头
            removed++;
            if (removed > M) {
                return false;
            }
        } else {
            // 跳到这块石头
            lastPos = stone;
        }
    }

    // 检查从最后一块石头到终点的距离
    return L - lastPos >= maxDist;
}

int main() {
    int L, N, M;
    cin >> L >> N >> M;

    vector<int> stones(N);
    for (int i = 0; i < N; ++i) {
        cin >> stones[i];
    }

    int low = 0, high = L;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (check(stones, mid, M, L)) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }

    cout << high;
    return 0;
}

1561. 买木头

为了解决这个问题,我们可以采用二分查找的方法来寻找满足条件的最长木头长度。我们首先定义一个判断函数,用来检查给定长度的木头是否能满足客户要求的数量。然后,使用二分查找在可能的长度范围内查找最大满足条件的长度。

这个问题的关键在于,给定一个长度,如何快速判断出所有供应商提供的木头能否满足客户要求的数量。对于每个供应商,我们可以计算出他们的木头被切成给定长度后能提供多少根木头,然后将所有供应商提供的数量加起来,与客户要求的数量进行比较。

接下来是C++代码的实现:

#include <bits/stdc++.h>
using namespace std;
int n,m,l1,s1;
bool check(int length,const vector<int>& Lnum,const vector<int>& Snum)
{
	int total = 0;
	for(int i=1; i<=n; i++)
	{
		total += (Lnum[i] / length) * Snum[i]; 
	}
	return total >= m;
}
int BinarySearch(const vector<int>& Lnum,const vector<int>& Snum)
{
	int left = 1 ,  right = 10000;
	
	while(left<=right)
	{
		int mid = left + (right - left) / 2;
		if(check(mid,Lnum,Snum)){
			left = mid + 1;
		}
		else
		{
			right = mid - 1;
		}
	}
	return right;
}
int main()
{
	cin>>n>>m>>l1>>s1;	
	vector<int> Lnum(n+1),Snum(n+1);
	Lnum[1] = l1;
	Snum[1] = s1;
	for(int i=2; i<=n; i++)
	{
		Lnum[i] = ((Lnum[i-1] * 37011 + 10193) % 10000 ) + 1;
		Snum[i] = ((Snum[i-1] * 73011 + 24793) % 100) + 1;
	}
	
	cout<<BinarySearch(Lnum,Snum);
	return 0;
 } 

这段代码首先读入所有输入数据,然后使用二分查找算法来找到满足条件的最大长度。check函数用于判断给定长度是否能满足客户的需求。通过迭代调整搜索范围,直到找到最大可能的长度。

1910. 愤怒的奶牛

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool check(const vector<int>& stalls, int C, int minDist) {
    int lastPos = stalls[0];
    int cows = 1; // 第一头牛已经放置在第一个隔间
    for (int i = 1; i < stalls.size(); ++i) {
        if (stalls[i] - lastPos >= minDist) {
            cows++;
            lastPos = stalls[i];
            if (cows >= C) return true;
        }
    }
    return false;
}

int main() {
    int N, C;
    cin >> N >> C;

    vector<int> stalls(N);
    for (int i = 0; i < N; ++i) {
        cin >> stalls[i];
    }
    sort(stalls.begin(), stalls.end());

    int low = 1, high = stalls[N-1] - stalls[0];
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (check(stalls, C, mid)) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }

    cout << high;
    return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天秀信奥编程培训

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值