前提:一个非降序列
lower_bound()函数使用:
参数:
1、数组元素的地址(起始搜索位置)e.g. a + i;
2、数组元素的地址(末尾搜索位置)e.g. a + n;
3、二分查找的数
返回值:返回第一次大于等于所查找数的地址
(在函数后面减去数组名(数组起始地址即可获得下标)
upper_bound()函数
返回值:返回第一个大于查找值的地址
应用例:三足鼎立问题
当三个国家中的任何两国实力之和都大于第三国的时候,这三个国家互相结盟就呈“三足鼎立”之势,这种状态是最稳定的。
现已知本国的实力值,又给出 n 个其他国家的实力值。我们需要从这 n 个国家中找 2 个结盟,以成三足鼎立。有多少种选择呢?
输入格式:
输入首先在第一行给出 2 个正整数 n(2≤n≤105)和 P(≤109),分别为其他国家的个数、以及本国的实力值。随后一行给出 n 个正整数,表示n 个其他国家的实力值。每个数值不超过 109,数字间以空格分隔。
输出格式:
在一行中输出本国结盟选择的个数。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL a[N], res;
int main()
{
int n, x;
cin >> n >> x;
for(int i = 0; i < n; i++) scanf("%lld", &a[i]);
sort(a, a + n);
for(int i = 0; i < n; i++)
{
int low = upper_bound(a + i + 1, a + n, abs(x - a[i])) - a;
int up = lower_bound(a + i + 1, a + n, a[i] + x) - a;
res += up - low;
}
cout << res << endl;
return 0;
}
针对例题进行分析:
1.为什么l用upper而r用lower?
给出两个数x,y,我们需要找abs(x-y)<元素个数<x+y,中的元素个数,所以我们需要先找到第一个比abs(x-y)大的(严格大,没有=号)数的下标,同时这个下标是可以取到的(因为这个下标代表的这个数本身就比abs(x-y)大),这样左边的值我们就找到了,下面需要找右面的值。如果有一串序列是a[]={0,3,3,3,3,4},如果我们在求右边边界值的时候也用upper同时我们x+y在这里为3的话,那我们找的就是a[5],也就是4那个值所在的下标(因为upper是严格大于),但是很明显之前还有很多3,这些3同样是不能取的,因为我们要找的是两边之和大于第三边(严格大于而不是大于等于),所以如果我们用upper的话,我们找到的是严格大于x+y的,我们不确定在严格大于x+y之前有没有和x+y相等的数,这样一来问题就会变得麻烦,所以再求右边边界值的时候我们用lower,即找第一个>=x+y的数,这样一来r-1就一定是最后一个比x+y小的值的下标,同时我们sum+=的时候加的是r-l,这样一来就刚好是这个区间内元素的个数,因为这个区间就是[l,r).
2.为什么upper和lower的时候起始点都是a + i + 1而不是a + 1?
因为会有重复
如果有一下一组数据
7 30
15 16 27 35 42 51 92(顺序已经排好)
如果我们已知两边分别为30和16的话,我们应该找位于区间(14,46)的元素的个数,第一个数字15显然在这个区间中,但是如果按照代码从a + i + 1开始找的话不就找不到15了吗?很明显不会的,因为30 16 15这个组合在找30 15的时候就找完了,也就是说其实并不是30和16找15而是30和15找的16,因为15的位置比16靠前,所以这个组合在之前就已经被找过了,如果我们从a+1开始找的话,那15 16 30这个组合就被找过两次了,所以我们从a+i+1开始找,这样可以避免找到重复的组合。
3.为什么upper和lower的时候起始点都是a + i + 1而不是a + i?
因为a+i+1把a+i这个点排除在外了
例如一个序列 15 16 27 ,我们要已知两边为30和16,那么我们要找的区间就是(14,46),很显然16本身就在这个序列中,但是因为16已经作为一条边出现了,所以我们不能再考虑16,我们需要筛掉它,所以当我们遍历到第i个点时要从a+i+1开始,而不是从a+i开始