AtCoder Beginner Contest 459 题解
AtCoder Beginner Contest 459 题解
| 项目 | 内容 |
|---|---|
| 比赛时间 | 2026-05-23 |
| 比赛链接 | https://atcoder.jp/contests/abc459 |
| 题目数量 | A–G 共 7 题 |
比赛概览
本场整体难度适中:A、B 是入门热身题;C 需要抓住"全局删除"的懒惰处理技巧;D 是经典的重排字符串不相邻问题;E 结合树的遍历与组合计数;F 通过单调化变换 把问题转化为单调回归(PAVA),是区分度较高的一题;G 是全场的压轴题,综合了扩展欧几里得、奇偶性归约与凸分段线性函数的最小化,值得反复消化。
A - Hell, World!
题目链接:https://atcoder.jp/contests/abc459/tasks/abc459_a
题目大意
给定一个整数 (),输出字符串 HelloWorld 删除第 个字符后得到的字符串。
思路
这是最简单的字符串处理题,只要跳过原字符串中的第 个字符即可。
一个直观的做法是:令 HelloWorld, 为空串,从左到右扫描 ,若 就把 接到 末尾。这样 恰好是删掉第 个字符后的结果。
在 C++ 中还可以直接用 erase 一步完成:注意 C++ 的下标从 开始,所以要先执行 x--,然后 s.erase(s.begin() + x)。
边界情况: 时删除第一个字符, 时删除最后一个字符,上述做法都天然正确。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int x;
cin >> x;
x--;
string s = "HelloWorld";
s.erase(s.begin() + x);
cout << s << '\n';
return 0;
}复杂度分析
- 时间复杂度:(字符串长度固定为 )
- 空间复杂度:
B - 459
题目链接:https://atcoder.jp/contests/abc459/tasks/abc459_b
题目大意
给定 个小写字母组成的字符串 。对每个字符串,按"老式九键手机"的规则,根据它的第一个字符映射成一个数字字符:a,b,c → 2,d,e,f → 3,,w,x,y,z → 9。把所有映射得到的数字按顺序拼接并输出。
思路
规则就是手机键盘九宫格:从 a 到 z,每 3 个字母一组对应 2,3,4,5,6,8,而 7(pqrs)和 9(wxyz)一组有 4 个字母。
最简洁的实现是打一张长度 26 的查表字符串:
a b c d e f g h i j k l m n o p q r s t u v w x y z
2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 7 8 8 8 9 9 9 9即 "22233344455566677778889999",用 mp[s[0]-'a'] 即可得到答案。也可以手动生成这张表:对 2..9 循环,2..6,8 各写 3 个,7,9 各写 4 个。
由于每个字符串长度不超过 10,直接扫描即可,不需要任何优化。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string mp = "22233344455566677778889999";
int n;
cin >> n;
string ans;
for (int i = 0; i < n; i++) {
string s;
cin >> s;
ans += mp[s[0] - 'a'];
}
cout << ans << '\n';
return 0;
}复杂度分析
- 时间复杂度:
- 空间复杂度:
C - Drop Blocks
题目链接:https://atcoder.jp/contests/abc459/tasks/abc459_c
题目大意
有 个格子排成一排,初始都没有方块。处理 个操作:
1 x:在第 个格子上放 1 个方块;然后,若此时每个格子都至少有 1 个方块,则从每个格子中移除 1 个方块。2 y:输出方块数至少为 的格子个数。
。
思路
关键观察:跳过"全局删除"
如果每次都真的模拟"从所有格子各删一个方块",那么一次全局删除就要花 ,最坏情况下总代价是 ,无法接受。
注意到:全局删除对所有格子是同步进行的,删了多少次只影响"相对高度"的整体平移。所以我们完全不去真正删除,而是维护一个计数器 ,表示"已经发生了多少次全局删除"。那么格子的实际高度就是它的原始高度减去 。
记 为格子 的原始高度(即所有 1 x 操作在它上面的累加,不扣除任何东西)。当发生 次全局删除时,实际高度为 。
用频率数组维护"至少"计数
再维护 = "原始高度 的格子个数"。每当 1 x 把 从 增加到 时,只影响 :因为该格子原来就满足 ()的条件,而 ()的条件它还不满足。所以只需执行 B[a[x]]++。
全局删除发生的条件:每个格子实际高度 ,等价于每个格子原始高度 ,即 。因此 就是"满足 的最大 ",也就是所有格子原始高度的最小值。
当一次 1 x 操作把 增加到 且 时,说明此时所有格子的原始高度都 ,而刚才那格恰好就是 ,所以最小值恰好等于 ,令 即可。注意 每次最多增加 (因为最小值每次至多上升 1),所以这个维护是正确的。
回答询问
类型 2 询问 y:答案是"原始高度 的格子数",即 (若 ,显然为 ,因为原始高度不可能超过 )。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, Q;
cin >> N >> Q;
vector<int> a(N + 1, 0); // 原始高度
vector<int> B(Q + 2, 0); // B[h] = 原始高度 >= h 的格子数
int k = 0; // 已发生的全局删除次数
for (int qi = 0; qi < Q; qi++) {
int t, x;
cin >> t >> x;
if (t == 1) {
a[x]++;
B[a[x]]++;
if (B[a[x]] == N) k = a[x]; // 所有格子原始高度都 >= a[x]
} else {
int y = x;
if (y + k > Q) cout << 0 << '\n';
else cout << B[y + k] << '\n';
}
}
return 0;
}复杂度分析
- 时间复杂度:
- 空间复杂度:
D - Adjacent Distinct String
题目链接:https://atcoder.jp/contests/abc459/tasks/abc459_d
题目大意
给定由小写字母组成的字符串 。判断能否将 重排,使得没有两个相邻字符相同;若可以,输出任意一个这样的重排。共 组数据,,总长度 。
思路
可行性的判定
记 ,设出现次数最多的字符出现了 次。
必要性:若 ,则 : 个该字符之间需要至少 个其他字符来隔开,而其他字符总共只有 个,不够,因此必有某两个该字符相邻,无解。故必须 。
充分性:当 时,一定能构造出合法排列(见下面的贪心)。这是经典结论。
贪心构造
维护每个字母剩余的次数 ,以及上一次放入的字母 。每次从"不等于 的字母"中挑出剩余次数最多的一个放入答案,并令其次数减一。
为什么贪心永远成功? 关键在于不变量:
在每次放置之前,任意字母的剩余次数 ,其中 为剩余长度。
初始时由 保证。假设放置前不变量成立,我们放置的是"不等于 "中次数最多的字母 (次数为 ):
- 的次数变为 。若 为偶数,则 ,,成立;若 为奇数,则 ,,成立。
- 其他字母的次数不变。若 为偶数,,不变显然仍成立。若 为奇数(),新的上界是 ,唯一需要担心的是某字母次数恰为 的情形:
- 它不可能是 : 在上一步刚被放置,放置前它的次数为 ,而那一步的不变量(当时 ,上界 )要求 ,矛盾。
- 它也不可能是除 外的其他字母: 是"非 "字母中次数最多的,若某个非 字母次数为 ,则 即 ,会出现两个次数为 的字母,总次数 ,矛盾。
因此唯一可能为 的字母就是 ,而 恰好被减掉。不变量保持。
再说明贪心不会"卡死":若某步只能放 (即所有剩余字母都是 ),则剩余长度 ,由不变量 得 。若 且 ,说明只剩最后一位且它与前一位相同——但若前一状态是 (),贪心一定先放 ,所以这种卡死状态根本到不了。因此贪心总能完成。
注意:因为 被排除在候选之外,如果出现"唯一有剩余次数的字母恰好是 "且它次数 ,说明此前的不变量已经被破坏,这在 的前提下不会发生。
实现时每步扫描 26 个字母找最大值即可,总复杂度 。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
string s;
cin >> s;
int n = (int)s.size();
int cnt[26] = {0};
for (char c : s) cnt[c - 'a']++;
bool ok = true;
for (int i = 0; i < 26; i++)
if (cnt[i] > (n + 1) / 2) ok = false;
if (!ok) {
cout << "No\n";
continue;
}
string ans;
int prev = -1;
while ((int)ans.size() < n) {
int best = -1;
for (int i = 0; i < 26; i++) {
if (i == prev) continue;
if (cnt[i] > 0 && (best == -1 || cnt[i] > cnt[best])) best = i;
}
ans += char('a' + best);
cnt[best]--;
prev = best;
}
cout << "Yes\n" << ans << '\n';
}
return 0;
}官方题解还提供了等价的 Python 实现:
import sys
input = sys.stdin.readline
def solve():
s = input()[:-1]
n = len(s)
cnt = [0] * 26
for c in s:
cnt[ord(c) - ord("a")] += 1
for v in cnt:
if v > (n + 1) // 2:
print("No")
return
ans = []
prev = -1
while True:
idx = -1
val = 0
for i in range(26):
if i == prev:
continue
if val < cnt[i]:
val = cnt[i]
idx = i
if idx == -1:
break
ans.append(chr(ord("a") + idx))
cnt[idx] -= 1
prev = idx
print("Yes")
print("".join(ans))
for _ in range(int(input())):
solve()复杂度分析
- 时间复杂度:
- 空间复杂度:
E - Select from Subtrees
题目链接:https://atcoder.jp/contests/abc459/tasks/abc459_e
题目大意
给出一棵以 为根的树,顶点 有 个互不相同的糖果。 只松鼠 要从以 为根的子树中取出 个糖果,不同松鼠不能拿同一个糖果。求方案数模 ;若不可能,输出 。
约束:,,。
思路
关键观察:按后序遍历,逐步计数是独立的
松鼠的选择顺序不影响答案,所以让松鼠按顶点的后序遍历依次取糖。此时处理到顶点 时, 的所有后代(子树内的其他顶点)都已经取完了。
记以 为根的子树中:
当轮到松鼠 取糖时,它的后代松鼠已经从子树 里取走了 个糖果(不管它们具体取了哪几个,数量是固定的)。因此子树 中仍可选的糖果数为
于是松鼠 的选择数为二项式系数
这个数与后代松鼠的具体选择无关,因此总方案数就是所有 的乘积:
若存在某个 使 (子树糖果不够),则答案为 。
正确性:后序遍历保证每颗糖果只会被"最深的那只松鼠"拿走,方案的计数与按后序的逐步选择形成一一对应,且每一步的候选项数只依赖计数、不依赖具体哪几颗,故乘积公式成立。
计算
一次自底向上的遍历即可:后序遍历中,处理顶点 前,其所有儿子的 都已算好,直接累加。 可达 ,递归可能爆栈,用显式栈做迭代式后序遍历。
计算二项式系数
可能高达 ,远大于模数,因此不能预计算到 的阶乘。但 很小,可以用"下降幂"计算:
分子每个因子对模数取模后相乘,分母 预计算阶乘与逆元。总复杂度 ,满足 。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
ll modpow(ll b, ll e) {
ll r = 1;
b %= MOD;
while (e) {
if (e & 1) r = r * b % MOD;
b = b * b % MOD;
e >>= 1;
}
return r;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
cin >> N;
vector<vector<int>> ch(N);
for (int i = 1; i < N; i++) {
int p;
cin >> p;
ch[p - 1].push_back(i);
}
vector<ll> C(N), D(N);
for (int i = 0; i < N; i++) cin >> C[i];
for (int i = 0; i < N; i++) cin >> D[i];
// 预计算阶乘与阶乘逆元,范围到 max D
ll maxD = 0;
for (ll d : D) maxD = max(maxD, d);
vector<ll> fact(maxD + 1), invfact(maxD + 1);
fact[0] = 1;
for (int i = 1; i <= maxD; i++) fact[i] = fact[i - 1] * i % MOD;
invfact[maxD] = modpow(fact[maxD], MOD - 2);
for (int i = maxD; i >= 1; i--) invfact[i - 1] = invfact[i] * i % MOD;
// 迭代式后序遍历
vector<int> order;
stack<pair<int, int>> st;
st.push({0, 0});
while (!st.empty()) {
auto [v, vis] = st.top();
st.pop();
if (vis) {
order.push_back(v);
continue;
}
st.push({v, 1});
for (int u : ch[v]) st.push({u, 0});
}
vector<ll> S(N, 0), Tsum(N, 0);
ll ans = 1;
bool impossible = false;
for (int v : order) { // 后序:儿子先于父亲
ll s = C[v], t = D[v];
for (int u : ch[v]) { s += S[u]; t += Tsum[u]; }
S[v] = s;
Tsum[v] = t;
if (s < t) { impossible = true; break; }
ll k = D[v];
ll top = s - t + k; // = S_i - T_i + D_i
ll prod = 1; // 下降幂 top*(top-1)*...*(top-k+1)
for (ll j = 0; j < k; j++) prod = prod * ((top - j) % MOD) % MOD;
ans = ans * prod % MOD * invfact[k] % MOD; // 除以 k!
}
if (impossible) cout << 0 << '\n';
else cout << ans << '\n';
return 0;
}复杂度分析
- 时间复杂度:
- 空间复杂度:
F - -1, +1
题目链接:https://atcoder.jp/contests/abc459/tasks/abc459_f
题目大意
给定长度为 的序列 。每次操作可以选择 ,令 减 、 加 。求把 变成严格递增序列所需的最少操作次数。 组数据,。
思路
第一步:严格递增 弱递增
令(下标从 0 开始)
则 严格递增等价于 弱递增:
(最后一步用了整数性质。)而在 的视角下,一次操作仍是"把 1 从位置 移到位置 ": 减 1、 加 1,总和不变。问题变成:用最少次"向右搬一个单位"的操作,把 变成弱递增序列。
第二步:最优终态是唯一的——按段"压平"
考虑把一段长度 、和为 的连续区间压平成弱递增,且尽量"均匀"。满足弱递增、和为 的最均匀整数序列是
(它的累加和恰为 :这是经典的"尽量平均分配"。)
官方题解给出这样的结论:存在唯一的分割
使得对每段 (设段内和为 ),最终的弱递增序列取
直觉上就是把每段"压平"成尽可能均匀的弱递增序列,各段首尾相接仍是弱递增。这个分割可以用贪心(单调栈)构造:从左到右,每个元素先自成一个段 ;然后反复检查栈顶段与当前段:若栈顶段的最大值大于当前段的最小值,说明接起来不满足弱递增,就把两段合并(长度、和都相加),继续向上检查。
对段 ,其压平后的最小值(第一个元素)是 ,最大值(最后一个元素)是 。合并条件即为
注意 可能为负,要用向下取整。
第三步:最少操作数
考虑势能 (0 下标)。一次把 1 从 移到 的操作使 恰好增加 1:
因此任何把 变成 的操作序列,其操作次数必然等于 。又因为每一步操作都不改变总和、只把质量右移,最终能到达的弱递增序列正是上述"压平"得到的唯一 (直观地:它让质量尽量留在左边,同时满足弱递增)。于是最少操作数就是
其中 都是变换后的值( 为压平结果)。直接按段展开计算即可。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll floordiv(ll num, ll den) { // 向下取整,den > 0
if (num >= 0) return num / den;
return -((-num + den - 1) / den);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<ll> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
a[i] -= i; // B_i = A_i - i,弱递增化
}
// 单调栈维护段 (len, sum),合并违反弱递增的相邻段
vector<pair<ll, ll>> seg;
for (ll v : a) {
ll len = 1, sum = v;
while (!seg.empty()) {
auto [plen, psum] = seg.back();
ll pMax = floordiv(psum + plen - 1, plen); // 栈顶段的最大值
ll curMin = floordiv(sum, len); // 当前段的最小值
if (pMax <= curMin) break;
seg.pop_back();
len += plen;
sum += psum;
}
seg.push_back({len, sum});
}
__int128 ans = 0;
ll idx = 0;
for (auto [len, sum] : seg) {
for (ll i = 0; i < len; i++) {
ll b = floordiv(sum + i, len); // 段内压平值
ans += (__int128)idx * (b - a[idx]);
idx++;
}
}
cout << (ll)ans << '\n';
}
return 0;
}官方题解给出的 Python 实现(思路相同,用队列实现合并):
from collections import deque
import sys
input = sys.stdin.readline
for _ in range(int(input())):
n = int(input())
a = list(map(int, input().split()))
for i in range(n):
a[i] -= i
d = deque()
for v in a:
now = (1, v)
while d:
tail = d[-1]
if (tail[1] + tail[0] - 1) // tail[0] <= now[1] // now[0]:
break
now = (now[0] + tail[0], now[1] + tail[1])
d.pop()
d.append(now)
b = []
for v in d:
length, s = v
for i in range(length):
b.append((s + i) // length)
ans = 0
for i in range(n):
ans += i * (b[i] - a[i])
print(ans)复杂度分析
- 时间复杂度:(每段至多入栈出栈一次),总计
- 空间复杂度:
G - Golf 2
题目链接:https://atcoder.jp/contests/abc459/tasks/abc459_g
题目大意
棋盘上有一个棋子,初始在 。一次操作可以把棋子移到满足 且 ,或 且 的位置 。判断能否到达 ,若能,输出最少操作次数,否则输出 。,,。
思路
(本题官方题解只有文字推导,无样例代码,以下实现依据该算法编写并经过大量对拍验证。)
第一步:gcd 归约与可行性
记 。每次移动的横、纵坐标变化量都是 或 ,因此 都必须是 的倍数;否则输出 。若都是倍数,把 同除以 ,此后 。
另外还要注意:除以 后若 都为奇数,则每步移动的横、纵坐标奇偶性相同,所以 必须同奇偶()。这个额外条件不需要单独特判,下面的奇偶枚举会自然处理掉。
第二步:把移动建模为符号和,并固定奇偶
把移动分成两类:第一类 次(),第二类 次()。
设第一类移动中 方向的符号和为 (则其 贡献为 ), 方向符号和为 (贡献 );第二类同理为 。则必须满足
同时 且 (因为 次符号求和,奇偶性等于 ),同理 且 。
对给定的 ,最小 (该值与 同奇偶,天然满足奇偶要求)。符号表总是可构造的:设需要 次 符号为 、 次 符号为 ,取 即可配平,故无额外约束。于是目标为
在所有满足上述两个方程的整数解上取最小值。
第三步:固定奇偶组合,用扩展欧几里得参数化
枚举 、(各取 ,共 4 种组合)。对第一个方程 ,先用扩展欧几里得求出特解 ,通解为
枚举 ( 变化 2 不改奇偶性)即可挑出满足 、 的特解。于是
对第二个方程 同理(对 做扩展欧几里得):
若某个 无法同时满足两个方程的奇偶条件(例如 均为奇数而 为奇数时,任何组合都会失败),则跳过该组合。
于是问题归结为:对每个可行组合,求
的最小值,再对全部组合取最小。
第四步:最小化凸分段线性函数
把定义域放宽到实数 。它是凸的、分段线性的、且随 趋于无穷,因此最小值一定取到。 的"折痕"(两个 的等值线)由四条直线给出:
为什么最小值点必是两条折痕的交点? 设 、。
- 在非折痕处 光滑,。而 的光滑片梯度只可能是 或 , 的只可能是 或 。由于 ,任意两者之和都不为零向量。
- 恰在一条折痕上时,次梯度是连接上述两个方向向量的线段,检查四种折痕情形可知该线段仍不与另一函数的梯度抵消为零。
因此最小值点必须同时落在至少两条折痕上,即四条直线中某两条的交点。只需枚举全部 个两两交点( 保证它们都相交)。
整数解离实数解很近。 官方题解证明:存在整数最小值点 与某个实数最小值点 满足
(证明思路: 的水平集 是凸多边形,把 展开可知其边斜率只能是 ,由此用反证可得该结论。)因此对每个交点,只需检查其 Chebyshev 邻域内的 个整数点,取 的最小值即可。
交点坐标为有理数,为避免浮点误差,用整数除法精确求 、(分子可能为负,用向下取整),再展开 。
总结算法流程
- 若 或 ,输出 ;否则全部除以 。
- 枚举 ,用扩展欧几里得求出满足奇偶条件的 与 ;任一不可行则跳过。
- 构造四条直线 ,枚举 6 个交点,对每个交点检查 整数邻域内的 值。
- 全部组合中取最小值输出;若无任何可行组合,输出 。
每组的复杂度为扩展欧几里得的 加上常数次 求值。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i128 = __int128_t;
// 扩展欧几里得:a*x + b*y = gcd(a,b)
ll extgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) { x = 1; y = 0; return a; }
ll x1, y1;
ll g = extgcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
return g;
}
// 向下取整除法,den > 0
ll floordiv(i128 num, i128 den) {
if (num >= 0) return (ll)(num / den);
return (ll)(-((-num + den - 1) / den));
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
ll A, B, X, Y;
cin >> A >> B >> X >> Y;
ll g = gcd(A, B);
if (X % g != 0 || Y % g != 0) {
cout << -1 << '\n';
continue;
}
A /= g; B /= g; X /= g; Y /= g;
ll u, v, u2, v2;
extgcd(A, B, u, v); // A*u + B*v = 1
extgcd(B, A, u2, v2); // B*u2 + A*v2 = 1
const ll INF = (1LL << 62);
ll best = INF;
for (int c1 = 0; c1 <= 1; c1++) {
for (int c2 = 0; c2 <= 1; c2++) {
// 方程1: A*e1 + B*e3 = X,奇偶性 e1≡c1, e3≡c2 (mod 2)
ll e1, e3;
bool ok1 = false;
for (int t = 0; t <= 1; t++) {
ll e1t = X * u + B * t;
ll e3t = X * v - A * t;
if ((e1t & 1) == c1 && (e3t & 1) == c2) {
e1 = e1t; e3 = e3t; ok1 = true; break;
}
}
if (!ok1) continue;
// 方程2: B*e2 + A*e4 = Y,奇偶性 e2≡c1, e4≡c2 (mod 2)
ll e2, e4;
bool ok2 = false;
for (int t = 0; t <= 1; t++) {
ll e2t = Y * u2 + A * t;
ll e4t = Y * v2 - B * t;
if ((e2t & 1) == c1 && (e4t & 1) == c2) {
e2 = e2t; e4 = e4t; ok2 = true; break;
}
}
if (!ok2) continue;
// 折痕直线:
// L1: 2B x - 2A y = e2 - e1
// L2: 2B x + 2A y = -e1 - e2
// L3: 2A x - 2B y = e3 - e4
// L4: 2A x + 2B y = e3 + e4
ll al[4] = {2 * B, 2 * B, 2 * A, 2 * A};
ll bl[4] = {-2 * A, 2 * A, -2 * B, 2 * B};
ll cl[4] = {e2 - e1, -e1 - e2, e3 - e4, e3 + e4};
auto eval = [&](ll n, ll m) -> ll {
ll t1 = llabs(2 * B * n + e1);
ll t2 = llabs(2 * A * m + e2);
ll t3 = llabs(e3 - 2 * A * n);
ll t4 = llabs(e4 - 2 * B * m);
return max(t1, t2) + max(t3, t4);
};
// 枚举四条直线的全部两两交点
for (int i = 0; i < 4; i++) {
for (int j = i + 1; j < 4; j++) {
i128 det = (i128)al[i] * bl[j] - (i128)al[j] * bl[i];
i128 xn = (i128)cl[i] * bl[j] - (i128)cl[j] * bl[i];
i128 yn = (i128)al[i] * cl[j] - (i128)al[j] * cl[i];
ll fx, fy;
if (det > 0) {
fx = floordiv(xn, det);
fy = floordiv(yn, det);
} else {
fx = floordiv(-xn, -det);
fy = floordiv(-yn, -det);
}
// 整数最小值点距实数最小值点 Chebyshev 距离不超过 2
for (ll n = fx - 2; n <= fx + 2; n++)
for (ll m = fy - 2; m <= fy + 2; m++)
best = min(best, eval(n, m));
}
}
}
}
if (best == INF) cout << -1 << '\n';
else cout << best << '\n';
}
return 0;
}复杂度分析
- 时间复杂度: 每组数据(扩展欧几里得),另有常数次(约 )的 求值
- 空间复杂度:
(本场题解中的参考代码均已通过官方样例测试;G 题实现额外通过了随机小数据与 BFS/枚举解的暴力对拍。)