Log函数与猜数
从一个1到100的序列中查找一个数,最坏要多少次才能找到?
方法1:1~n枚举
最简单的方法,效率低,最坏的情况枚举100次
方法2:每次从中间查询
- 先猜区间 [1,100] 的中间数 50;
- 判断是否猜中要找的数
- 如果大,则数字范围在 [1,49][1,49];
- 如果小,则在 [51,100][51,100];
每次反复继续猜测中间数
所以,最坏的猜测结果(不唯一):
100>50>25>12>6>3>1
最多只需要 7 次。
这就叫做二分查找。
log是个非常重要的函数,在这里我们用的是log base 2,而不是log base 10,那么表示出来是这样
那么,
大约 n 每乘上 2,logn 就加 1。
≈ 7
结论:在 n 个元素中使用二分查找定位一个元素,最坏最坏情况下最多要找 log(n) 次。接下来正式进入二分查找。
二分查找
在一个1~n的递增序列中,怎么用二分查找数字x的下标?
在一个没用重复元素的递增序列中,用二分查找的方法最多需要查找log(n)次,也就是在一个区间内l~r不断的选取中间下标(mid),直到mid等于需要查找的数。这种查找方法在最坏的情况下能以很少的执行次数找到数。
而C++的二分查找实现是这样的:
- 对于一个区间有左指针
和右指针
。
- 在所有数字代表的区间,尝试中间下标 (mid)。
- 如果中间下标对应的数字就是答案则输出答案。
- 如果数字太小,则继续处理右区间。
- 如果数字太大,则继续处理左区间。
程序中的重要变量:
- a[100010] 输入的有序递增序列
- n 序列中数字的个数
- x 要查询的数字
- l,r,mid 左指针,右指针,中间下表
- ans 答案,中间下标等于x时下标的数
#include <bits/stdc++.h>
int a[1000010];
using namespace std;
int main() {
int n, x;
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
int l = 1, r = n, ans = -1;
// 左指针是第一个元素的下标,右指针是最后一个元素的下标
while (l<=r) { // 只要左指针不大于右指针,就不断地循环。
int mid = (l+r)/2; // 中间下标是左右指针的平均值
if (a[mid] == x) { // 如果中间下标对应的值刚好是要找的那个
ans = mid; // 记录答案并返回
break;
} else if (a[mid]>x) // 如果中间下标对应的值比要找到更大
r = mid-1; // 右指针缩到中间下标的左边一个
else // 如果中间下标对应的值比要找到小
l = mid+1; // 左指针缩到中间下标的右边一个
}
cout << ans; // 输出答案
}
查找第一个出现的位置时,可以这样执行
while (l <= r) {
int mid = (l + r) / 2;
if (a[mid] == x) { // 如果中间的数字等于要找的
ans = mid; // 记录答案位置
r = mid-1; // 局限在左区间
} else if (a[mid] > x) // 如果中间数字大于要找的
r = mid-1; // 局限在左区间
else // 如果中间数字小于要找的
l = mid+1; // 局限在右区间
}
查找最后一个出现的位置时,可以这样执行
while (l <= r) {
int mid = (l + r) / 2;
if (a[mid] == x) {
ans = mid;
l = mid+1;
} else if (a[mid] > x)
r = mid-1;
else
l = mid+1;
}
P1102 A-B 数对
题面
题目背景
出题是一件痛苦的事情!
相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!
题目描述
给出一串正整数数列以及一个正整数 C,要求计算出所有满足 A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 N,C。
第二行,N 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 A−B=C 的数对的个数。
输入输出样例
输入 #1
4 1 1 1 2 3
输出 #1
3
题解
重点:
二分查找之前需要排序成有序队列。
这道题需要满足A-B=C查找所有书对的个数,所以算则先固定某个A(因为此时C已经是确定的),找到了满足的数字B时需查找如果有同样的数,那么这个数第一次出现的下表和最后一次出现的下标。
步骤:
读入数据
对序列进行排序
对于每一个固定的a[i]查找b,以及b出现第一次的下表和最后一次的下表
计算第一次和最后一次下表的区间 并计入ans
返回ans值
代码
#include <bits/stdc++.h>
int a[200010], n, c;
long long ans = 0;
using namespace std;
int findx(int k) { // 找到第一次出现的位置
int l = 1, r = n, ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if (a[mid] == k) { // 如果中间的数字等于要找的
ans = mid; // 记录答案位置
r = mid-1; // 局限在左区间
} else if (a[mid] > k) // 如果中间数字大于要找的
r = mid-1; // 局限在左区间
else // 如果中间数字小于要找的
l = mid+1; // 局限在右区间
}
return ans;
}
int findy(int k) { // 找到最后一次出现的位置
int l = 1, r = n, ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if (a[mid] == k) {
ans = mid;
l = mid+1;
} else if (a[mid] > k)
r = mid-1;
else
l = mid+1;
}
return ans;
}
int main() {
cin >> n >> c;
for(int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; i++) {
int x = findx(a[i] + c);
int y = findy(a[i]+c);
if(x == -1) continue;
ans += y-x+1;
}
cout << ans; // 输出答案
}