在 C++ 里用两个指针或迭代器(比如 beg 和 end)表示一个范围(序列里的一段),
规则是左闭右开,我们以符号 [beg, end) 表示。
std::lower_bound
设 [b, e) 是元素从小到大排好序的范围。lower_bound(b, e, x) 返回指向 [b, e) 内第一个不小于 x 的元素的指针(或迭代器),若不存在这样的元素则返回 e。
用二分查找来实现 lower_bound()
lower_bound(int* b, int *e, int x) {
while (b != e) {
int *m = b + (e - b) / 2;
if (*m < x)
b = m + 1;
else// !(*m < x)
e = m;
}
return e;
}
std::upper_bound
设 [b, e) 是元素从小到大排好序的范围。upper_bound(b, e, x) 返回指向 [b, e) 内第一个大于 x 的元素的指针(或迭代器),若不存在这样的元素则返回 e。
用二分查找来实现 upper_bound()
lower_bound(int* b, int *e, int x) {
while (b != e) {
int *m = b + (e - b) / 2;
if (x < *m)
e = m;
else// !(x < *m)
b = m + 1;
}
return e;
}
C++ 里默认的比较函数是小于号
lower_bound(b, e, v) 返回指向 [b, e) 里第一个满足“x 不小于 v”的元素 x 指针。
“x 不小于 v”用小于号来表达就是 ! xiaoyu(x, v)
upper_bound(b, e, v) 返回指向 [b, e) 里第一个满足“v 小于 x”的元素 x 的指针。
“v 小于 x”用小于号来表达就是 xiaoyu(v, x)。
自定义的比较函数也得是某种小于号
设元素类型是 T,查找的值的类型是 U(没错,两者可以不同)
对于 lower_bound(b, e, v, cmp)
cmp 的格式得是 bool cmp(T element, U value)
对于 upper_bound(b, e, v, cmp)
cmp 的格式得是 bool cmp(U value, T element)
Tips
一定要熟练使用 lower_bound() 和 upper_bound()
能用 lower_bound() 或 upper_bound() 时最好不要手写二分查找。
lower_bound 的比较函数和 upper_bound 的比较函数格式不同。
朴素的二分查找(查询一个有序的序列中第一个大于/小于某个值的元素)
通常用在解题的某个步骤。
掌握二分查找就能更顺利地解题。
例题:相交的区间
给你数轴上的 个区间。第 个区间是 。有多少个数对 ()满足第 个区间和第 个区间相交?
限制
样例
输入
3
1 5
7 8
3 7
输出
2
解释
和 相交, 和 相交。
思考一下(5 分钟)
思路
这题有多种解法。我们介绍一个简便思路。
两个区间相交的情况有点多。(其实也不多)
考虑不相交的区间有多少对。
对一个区间 ,计算完全在它左边的区间有多少个。
即有多少个 小于 。
代码
voidsolve(){
int n; cin >> n;
vector<int> l(n), r(n);
for (int i = 0; i < n; i++)
cin >> l[i] >> r[i];
sort(r.begin(), r.end());
longlong ans = 0;
for (int i = 0; i < n; i++) {
auto it = lower_bound(r.begin(), r.end(), l[i]);
ans += it - r.begin();
}
cout << (longlong) n * (n - 1) / 2 - ans;
}
template<typename F>
longlongbinary_search(F check, longlong ok, longlong ng){
while (abs(ok - ng) > 1) {
longlong x = (ok + ng) / 2;
if (check(x))
ok = x;
else
ng = x;
}
return ok;
}
我们只写 check 函数,确定 ok 和 ng 这两个参数。调用 binary_search,
代码
intmain(){
int n, k;
cin >> n >> k;
vector<longlong> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
auto check = [&](longlong x) {
longlong sum = 0;
for (longlong y : a)
sum += min(y, x);
return x * k <= sum;
};
longlong l = 1, r = 3e17 / k;
cout << binary_search(check, l, r) << '\n';;
}
double f[maxn][2];
int g[maxn][2];
// ... 把缺少的代码补上auto check_avg = [&](double x) {
f[0][0] = 0;
f[0][1] = b[0];
for (int i = 1; i < n; i++) {
f[i][0] = f[i - 1][1];
f[i][1] = b[i] + max(f[i - 1][0], f[i - 1][1]);
}
returnmax(f[n - 1][0], f[n - 1][1]) >= 0;
};
double l = 1, r = 1e9;
for (int t = 0; t < 100; t++) {
double mid = (l + r) / 2;
if (check_avg(mid))
l = mid;
else
r = mid;
}
cout << l << '\n';
auto check_median = [&](int x) {
vector<int> b(n);
for (int i = 0; i < n; i++)
if (a[i] >= x) b[i] = 1;
else b[i] = -1;
g[0][0] = 0;
g[0][1] = b[0];
for (int i = 1; i < n; i++) {
g[i][0] = g[i - 1][1];
g[i][1] = b[i] + max(g[i - 1][0], g[i - 1][1]);
}
returnmax(g[n - 1][0], g[n - 1][1]) > 0;
};
cout << binary_search(check_median, 1, 1e9 + 1) << '\n';
代码
voidsolve(){
int n, m; longlong k; cin >> n >> m >> k;
vector<longlong> a(n);
for (int i = 0; i < n; i++) { cin >> a[i]; k -= a[i]; }
if (m == n) {
for (int i = 0; i < n; i++) cout << 0 << ' ';
return;
}
auto b = a; sort(b.begin(), b.end());
vector<longlong> s(n + 1); //前缀和for (int i = 0; i < n; i++)
s[i + 1] = s[i] + b[i];
for (longlong x : a) {
auto check = [&](longlong t) {
int i = upper_bound(b.begin(), b.end(), x + t) - b.begin();
if (n - i >= m) returnfalse;
int cnt = m - (n - i);
longlong have;
if (b[n - m - 1] >= x) have = s[i] - s[i - cnt];
else have = s[i] - s[i - cnt - 1] - x;
return have + k - t < cnt * (x + t + 1);
};
longlong t = binary_search(check, k + 1, -1);
if (t == k + 1) t = -1;
cout << t << ' ';
}
}
voidsolve(){
int n, m;
vector<longlong> a(n), b(m);
vector<int> type(n);
// 输入 ...
vector<longlong> p[4];
for (int i = 0; i < n; i++)
p[type[i]].push_back(a[i]);
for (int i = 1; i <= 3; i++)
sort(p[i].begin(), p[i].end());
// ... 对 b 排序并去重
m = (int) b.size();
// ... check
cout << binary_search(check, 2e18, -1) << '\n';
}
auto check = [&](ll x) -> bool {
vector<int> vis(m);
int i = 0;
for (int y : p[1]) {
// y - x <= b[i] <= yfor (; i < m && b[i] <= y; i++)
if (b[i] >= y - x)
vis[i] = 1;
}
i = 0;
for (int y : p[2]) {
// y <= b[i] <= y + xfor (; i < m && b[i] <= y + x; i++)
if (b[i] >= y) vis[i] = 1;
}
i = 0;
for (int y : p[3]) {
while (i < m && vis[i]) i++;
if (i < m) {
longlong len; // y往右最多能够到多远// 计算lenif (y <= b[i]) len = x;
else {
if (b[i] + x < y) returnfalse;
// option 1: 先往右走,再往左走// option 2: 先往左走,再往右走
len = max((x - (y - b[i])) / 2, x - 2 * (y - b[i]));
i++;
}
while (i < m && b[i] <= y + len) i++;
}
}
for (; i < m; i++)
if (!vis[i]) returnfalse;
returntrue;
};
vector<int> g[maxn];
int n;
int ddl[maxn];
int parent[maxn];
longlong a[maxn], b[maxn], c[maxn];
using i128 = __int128;
i128 calc(int i, int s, int t){
if (c[i] >= 0 || b[i] + c[i] * t > 0) // 等差数列return (i128) (b[i] + s * c[i] + b[i] + t * c[i]) * (t - s + 1) / 2;
int t0 = (b[i] - 1) / -c[i];
if (s > t0)
return t - s + 1;
// [s..t0], [t0+1, t]return (i128) (b[i] + s * c[i] + b[i] + t0 * c[i]) * (t0 - s + 1) / 2 + (t - t0);
}
voiddfs(int u, int p){
parent[u] = p;
for (int v : g[u])
if (v != p)
dfs(v, u);
}
intmain(){
// ... 输入dfs(1, 0);
cout << binary_search(check, 1e9, n - 1) << '\n';
}
boolcheck(int x){
for (int i = 1; i <= n; i++) {
// 计算 ddl[i]int l = 0, r = x + 1;
while (l + 1 < r) {
int mid = (l + r) / 2;
if (calc(i, mid, x) >= a[i])
l = mid;
else
r = mid;
}
if (l == 0) returnfalse;
ddl[i] = l;
}
priority_queue<pair<int,int>> q;
vector<int> deg(n + 1);
for (int i = 2; i <= n; i++)
deg[parent[i]]++;
for (int i = 1; i <= n; i++)
if (deg[i] == 0)
q.push({ddl[i], i});
int t = x;
while (!q.empty()) {
auto p = q.top();
q.pop();
if (t == 0) returnfalse;
t = min(t, p.first) - 1;
int pa = parent[p.second];
if (--deg[pa] == 0) {
q.push({ddl[pa], pa});
}
}
returntrue;
}
intmain(){
int n, m; longlong k; cin >> n >> m >> k;
vector<longlong> a(n);
for (int i = 0; i < n; i++) { cin >> a[i]; k -= a[i]; }
if (m == n) {
for (int i = 0; i < n; i++) cout << 0 << ' ';
return0;
}
auto b = a; sort(b.begin(), b.end());
vector<longlong> s(n + 1); //前缀和for (int i = 0; i < n; i++)
s[i + 1] = s[i] + b[i];
for (longlong x : a) {
auto check = [&](longlong t) {
int i = upper_bound(b.begin(), b.end(), x + t) - b.begin();
if (n - i >= m) returnfalse;
int cnt = m - (n - i);
longlong have;
if (b[n - m - 1] >= x) have = s[i] - s[i - cnt];
else have = s[i] - s[i - cnt - 1] - x;
return have + k - t < cnt * (x + t + 1);
};
longlong t = binary_search(check, k + 1, -1);
if (t == k + 1) t = -1;
cout << t << ' ';
}
}
Bonus
这题有更快的解法。
习题:磨牙
小明有 颗牙: 颗上牙和 颗下牙。
从左到右数,第 颗上牙的长度是 ,第 颗下牙的长度是 。
若下列两条件成立,则称他的牙齿咬合良好:
存在一个整数 使得对每个 都有 。
对每个整数 都有 。
小明可进行下述操作任意多次
花 元钱使用磨牙机,使他的某颗长度大于等于 的牙的长度减少 。
小明要让他的牙咬合良好最少要花多少钱?
限制
习题:平方价格
有 种产品,每种有 个。
每种产品你可以买任意非负整数个。买 个第 种产品要花 元。
若你有 元,最多可以买多少个产品?
限制
都是整数。
abc355d
---
# 例题:数对的数位和
对于正整数 $x$,令 $f(x)$ 为 $x$ 的数字之和。例如,$f(158) = 1 + 5 + 8 = 14$,$f(2023) = 2 + 0 + 2 + 3 = 7$,$f(1) = 1$。
给你一个正整数序列 $A = (A_1, \ldots, A_N)$。求 $\sum_{i=1}^N\sum_{j=1}^N f(A_i + A_j)$。
###### 限制
- $1\leq N\leq 2\times 10^5$
- $1\leq A_i < 10^{15}$
---
# 样例
<div class=columns><div>
输入
```
2
53 28
```
</div><div>
输出
```
36
```
</div></div>
$$\begin{aligned}
&\sum_{i=1}^N\sum_{j=1}^N f(A_i + A_j) \\
& = f(A_1+A_1)+f(A_1+A_2)+f(A_2+A_1)+f(A_2+A_2) \\
& =f(106) + f(81) + f(81) + f(56) \\
&= 7+9+9+11 \\
&=36
\end{aligned}
$$
---
# 思考一下(10 分钟)
---
# 一个结论
设 $x$,$y$ 是正整数。有
$$
f(x + y) = f(x) + f(y) - 9 \times (列竖式算\ x+y\ 时进位的次数)
$$
列竖式算 $x+y$ 时,每发生一次进位数字和损失 $9$.
你觉得对吗?为什么?
接下来怎么办?
---
# 进位的次数
问题化为对全部数对 $A_i, A_j$,$A_i + A_j$ 时一共进位了多少次?
分别考虑每个数位。
有多少对 $i, j$ 满足 $A_i + A_j$ 会在个位发生进位?
有多少对 $i, j$ 满足 $A_i + A_j$ 会在十位发生进位?
……
---
# 计算进位的次数
列竖式计算 $A_i + A_j$ 会第 $k$ 位(个位是第 $0$ 位,十位是第 $1$ 位,……)发生进位相当于说
$$
(A_i \bmod 10^{k+1}) + (A_j \bmod 10^{k+1}) \ge 10^{k+1}
$$
这里 $n \bmod m$ 表示 $n$ 除以 $m$ 的余数。
:bulb: 你会了吗?
---
# 解法
- 对每个数位计算这一位上发生的进位的总次数。
- 对第 $k$ 位,算出 $A_i \bmod 10^{k+1}$($1 \le i \le N$),得序列 $R_k$。
- 问题化为 $R_k$ 里有多少对数之和大于等于 $10^{k+1}$。
- 对 $R_k$ 排序,然后用**二分查找**或**双指针**解决上述问题。
- 用二分查找更好写一点,你可以用 std::lower_bound()。
---
# 代码
<div class=columns><div>
```cpp
int main() {
// ...
long long sum = 0;
for (long long x : a) {
long long y = x;
while (y)
sum += y % 10, y /= 10;
}
long long carry = 0;
long long t = 1;
for (int _ = 0; _ < 15; _++) {
t *= 10;
vector<long long> b(n);
for (int i = 0; i < n; i++)
b[i] = a[i] % t;
sort(b.begin(), b.end());
//
}
cout << sum * 2 * n - 9 * carry << '\n';
}
```
</div><div>
```cpp
int main() {
// ...
long long sum = 0;
for (long long x : a) {
long long y = x;
while (y)
sum += y % 10, y /= 10;
}
long long carry = 0;
long long t = 1;
for (int _ = 0; _ < 15; _++) {
t *= 10;
vector<long long> b(n);
for (int i = 0; i < n; i++)
b[i] = a[i] % t;
sort(b.begin(), b.end());
// 二分查找
for (int x : b)
carry += b.end() - lower_bound(b.begin(), b.end(), t - x);
}
cout << sum * 2 * n - 9 * carry << '\n';
}
```
</div></div>
[abc373_e](https://atcoder.jp/contests/abc373/tasks/abc373_e) How to Win the Election