B. Maximum Value 解题思路
核心问题分析
题意干净得不能再干净:给一个长度为 的序列 ,要从里面挑两个数 (下标可以相同),在满足 的前提下,最大化取模的结果
数据范围是 ,但是真正的命门在值域上:。直接 枚举所有数对, 次,想都别想。看到“值域小、要取模、要枚举倍数”这几个关键词凑在一起,脑子里应该立刻蹦出四个字——调和级数。
1. 把取模翻译成“区间里挑最大”
我们先盯死被除数 (代码里循环变量叫 it)。固定了除数 之后, 这个值随 怎么变?
把整个值域按 切成一段一段:
在第 段 里,任意一个 模 的结果就是 。也就是说——在同一段里, 越大,余数越大!余数和 在段内是严格线性递增的。
所以对每一段,我们只关心一件事:这一段里实际存在的最大的那个 是谁。把它模一下 ,就是这一段能贡献的最大余数。把所有段、所有 的贡献取个 max,就是答案。是不是一下子就清爽了?
2. 调和级数:为什么枚举倍数不会爆
有同学要担心了:对每个 都把值域切成那么多段,会不会很慢?
来算笔账。对一个具体的 ,段数大约是 个。把所有去重后的 加起来,总段数大约是
这就是经典的调和级数求和,。 时, 大约是 量级,外加每段一次二分的 ,完全在一秒内能跑完。注意代码开头先 sort 再 unique 去了重——这一步很关键,重复的 没必要重复枚举,去重之后 的种类数被压住了,调和级数的复杂度才稳。
sort(all(a));
a.erase(unique(all(a)),a.end());3. 用二分精准捞出“每段的最大 ”
知道了要找每段的最大 ,怎么找?数组已经排好序了,二分包打天下。
看代码这个精妙的小动作:
int temp = it;
while (temp <= MAXN) {
temp += it;
int now = *prev(lower_bound(all(a), temp));
max_n = max(max_n, now % it);
}这里 it 是 。循环里 temp 从 开始,每次加 ,依次取到每一段的右端点 。对于右端点 temp:
lower_bound(all(a), temp)找到第一个 的位置;prev(...)往前退一格,拿到的就是严格小于temp的那个最大的 ——恰好是落在 这一段(及更左)里的最大值;now % it即是这个 对 的余数,拿去更新答案。
为什么 temp 从 起步(先 temp += it 再用)?因为 这一段里所有数模 的结果,最大的也就出现在右端点 的左边,正好被第一次循环的 prev(lower_bound(..., 2a_j)) 捞到。每个段右端点都查一次最大值,所有段扫完,这个 的最优贡献就稳稳到手了。
小俏皮提醒:
prev(begin())是会出事的。但这里因为temp至少是 ,而数组里一定有 本身(它就是it),所以lower_bound绝不会落在最开头,prev永远有得退,丝毫不影响正确性。
4. 边界与初值的小心思
max_n初值设成 。因为完全可以取 ,此时 ,答案天然下界就是 (比如全部数都相等的退化情况,输出就是 )。while (temp <= MAXN)用MAXN = 1e6 + 10卡住值域上界,保证temp不会无限加下去,段数被值域天花板掐断。- 拿样例
3 4 5走一遍:去重还是{3,4,5}。除数 时,段右端点 ,prev(lower_bound(...,6))拿到 ,;除数 、 同理算下来都不超过 。最终答案 ,和样例对上了。
整体复杂度 (值域调和级数求和),排序去重的 完全被它盖过,对本题数据轻松通关。
CPP 代码实现
// B. Maximum Value
#include <bits/stdc++.h>
#define lg(x) (63 - __builtin_clzll(x))
#define all(x) (x).begin(), (x).end()
#define low_bit(x) ((x) & (-x))
#define pb push_back
#define db long double
// #define int long long
#define sz(x) (int)x.size()
#define endl "\n"
using namespace std;
const int MAXN = 1e6 + 10;
void solve() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(all(a));
a.erase(unique(all(a)),a.end());
int max_n = 0;
for (auto it : a) {
int temp = it;
while (temp <= MAXN) {
temp += it;
int now = *prev(lower_bound(all(a), temp));
max_n = max(max_n, now % it);
}
}
cout << max_n << endl;
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
}