预备知识:
本文默认读者学过最基本的二分。
本文将层层深入讲解二分变式
首先是两个二分法的简单变式(数组从小到大排序)
// 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;
}
(还有几道题,等我刷完