官方题解|巅峰赛#18
2025-03-02 22:11:31
发布于:浙江
ACGO 巅峰赛#18 题解
本场巅峰赛的所有题目的难度评级为:
题目编号 | 题目标题 | 难度 |
---|---|---|
A. | 数组中未出现的 K 的最小倍数 | 入门 |
B. | 美丽数 III | 普及- |
C. | 元素距离积(简单) | 入门 |
D. | 元素距离积(困难) | 普及 |
E. | 最小化两数组的距离 | 普及+/提高 |
F. | 统计操作后不同的序列 | 提高+/省选- |
本场比赛题目侧重对于选手数学能力的考察,其中:
题 和 题考察了拆解「绝对值」和根据数据范围变换代数式求解的方法。
题考察了对于「平均数」的处理技巧和方法。
以上方法皆为算法竞赛中常见的数学处理方法。
题目解析
A - 数组中未出现的 K 的最小倍数
模拟;枚举
我们可以直接从小到大枚举 的倍数 ,并检检查数组中是否存在 即可;
由于 和 的值域都比较小,所以 最多不会超过 。
#include <bits/stdc++.h>
constexpr int N = 100;
int main() {
int n, k;
std::cin >> n >> k;
int a[N];
for (int i = 0; i < n; ++i)
std::cin >> a[i];
for (int x = 0; x <= N + k; x += k) {
bool flag = true;
for (int i = 0; i < n; ++i)
if (x == a[i]) flag = false;
if (flag == true) {
std::cout << x << "\n";
break;
}
}
return 0;
}
B - 美丽数 III
数学;枚举
令 为「美丽数」的位数,不难发现 ,同时 的「美丽数」有 个, 的「美丽数」也有 个……
「美丽数」每增长 位,就会多出 个「美丽数」,所以要想知道第 个「美丽数」,我们就可以先计算出其位数 ,再枚举 和 即可。
#include <bits/stdc++.h>
int main() {
int n; std::cin >> n;
int m = (n - 1) / 36 + 1, k = (n - 1) % 36;
for (int a = 1; a <= 9; ++a)
for (int b = a + 1; b <= 9; ++b) {
if (k == 0) {
std::cout << a;
for (int i = 0; i < m; ++i)
std::cout << b;
return 0;
}
k -= 1;
}
return 0;
}
C - 元素距离积(简单)
模拟;枚举
按照题意,使用双重循环枚举 和 计算答案;计算绝对值可以使用内置函数 abs
。整个代码时间复杂度为 。
#include <bits/stdc++.h>
int main() {
int n; std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++i)
std::cin >> a[i];
int res = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
res += std::abs(j - i) * std::abs(a[i] - a[j]);
std::cout << res << "\n";
return 0;
}
D - 元素距离积(困难)
数学;枚举;计数
我们先处理题目式子中的绝对值。
由于 ,所以原式可化为:
注意到,本题 的范围较小,我们可以围绕此,对上式进行变换,从而高速计算。
对于上式,我们考虑枚举 ,同时
令 代表 之前所有元素中值 出现的次数;
令 代表 之前所有元素中值为 的下标之和;
那么可以将上式转化为:
计算上式的时间复杂度为 ,这样我们便可以高效地计算出答案。
#include <bits/stdc++.h>
using i64 = long long;
constexpr int M = 500;
int main() {
int n; std::cin >> n;
i64 res = 0;
std::vector<i64> cnt(M + 1), sum(M + 1);
for (int i = 1; i <= n; ++i) {
int x; std::cin >> x;
for (int y = 1; y <= M; ++y)
res += (i * cnt[y] - sum[y]) * std::abs(x - y);
cnt[x] += 1;
sum[x] += i;
}
std::cout << res * 2 << "\n";
return 0;
}
E - 最小化两数组的距离
数学;枚举;二分
对于只交换 对二元组的情况,我们直接枚举 中要交换的二元组和 中要交换的二元组即可,时间复杂度 。
我们主要考虑交换 对二元组的情况。
首先考虑 ,在未交换交换元素时,令:
此时若交换的 对二元组分别为 和 ,那么:
对于交换 对二元组的情况,我们只需要将 中的元素两两结合, 中的元素两两结合,就可以把选择 对二元组交换转化为选择 对两两结合后的二元组交换。
那么对于上式,我们可以枚举 ,二分 。
具体的,我们把 的两两结合的 排序去重,然后找到最大的 使得 ,此时 和 即为与 交换后能够使得 最小的两个元素,其中 或 可能有一个不存在,要注意判别。
对于 同理。
整个算法时间复杂度 。
#include <bits/stdc++.h>
using pair = std::pair<int, int>;
constexpr int N = 1000;
int main() {
int n; std::cin >> n;
std::vector<int> x1(n), y1(n), x2(n), y2(n);
for (int i = 0; i < n; ++i)
std::cin >> x1[i] >> y1[i];
for (int i = 0; i < n; ++i)
std::cin >> x2[i] >> y2[i];
int diffS = 0, diffT = 0;
for (int i = 0; i < n; ++i) {
diffS += x1[i] - x2[i];
diffT += y1[i] - y2[i];
}
int s = std::abs(diffS), t = std::abs(diffT);
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j) {
int ns = std::abs(diffS - 2 * (x1[i] - x2[j]));
int nt = std::abs(diffT - 2 * (y1[i] - y2[j]));
std::tie(s, t) = std::min(pair{s, t}, pair{ns, nt});
}
std::vector<int> stX, stY[N + 1];
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j) {
int x = x2[i] + x2[j], y = y2[i] + y2[j];
stX.push_back(x);
stY[x].push_back(y);
}
std::sort(stX.begin(), stX.end());
stX.erase(std::unique(stX.begin(), stX.end()), stX.end());
for (auto &a : stY) {
std::sort(a.begin(), a.end());
a.erase(std::unique(a.begin(), a.end()), a.end());
}
auto nearest = [&](std::vector<int> &a, int t) {
int p = -1;
for (int i = 1 << 10; i > 0; i >>= 1)
if (p + i < a.size() and t + a[p + i] * 2 < 0) p += i;
std::vector<int> res;
if (p >= 0) res.push_back(a[p]);
if (p + 1 < a.size()) res.push_back(a[p + 1]);
return res;
};
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j) {
int sx1 = x1[i] + x1[j], sy1 = y1[i] + y1[j];
for (auto &sx2 : nearest(stX, diffS - 2 * sx1))
for (auto &sy2 : nearest(stY[sx2], diffT - 2 * sy1)) {
int ns = std::abs(diffS - 2 * (sx1 - sx2));
int nt = std::abs(diffT - 2 * (sy1 - sy2));
std::tie(s, t) = std::min(pair{s, t}, pair{ns, nt});
}
}
std::cout << s << ' ' << t << '\n';
return 0;
}
F - 统计操作后不同的序列
数学;前缀和;枚举;计数
每次操作后都可以得到一个序列, 二元组的总数量是 。
我们考虑将其中操作后得到的重复序列去除掉。
令 ,那么对于 或 的情况,我们将 向右边移动 或将 向左移动 ,操作后的序列不会发生变化。
我们考虑找到那些使得 的平均值与 或 相等的 将其从总数中去掉。
具体地,对于 ,进行如下操作:
定义 并构造 的前缀和序列 。
对于 :
-
若 ,则从答案中减去满足 且 的 的个数。
对应平均值为 、右端为 而左端任意的情况。 -
若 ,则从答案中减去满足 且 且 的 的个数。
对应于平均值为 、右端不为 而左端为 的情况。
对于每个 的计算时间复杂度为 。
因此,整体的时间复杂度为 。
#include <bits/stdc++.h>
using i64 = long long;
constexpr int M = 30;
int main() {
int n; std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++i)
std::cin >> a[i];
i64 res = n * (n + 1LL) / 2;
for (int x = 1; x <= M; ++x) {
std::vector<int> b(n + 1), s(n + 1);
for (int i = 1; i <= n; ++i)
b[i] = a[i] - x;
std::partial_sum(b.begin(), b.end(), s.begin());
const int DELTA = n * M;
auto f = [&](int v) {return DELTA + v;};
std::vector<int> zero(DELTA * 2 + 1);
std::vector<int> nzero(DELTA * 2 + 1);
for (int i = 1; i <= n; ++i)
if (b[i] == 0) {
zero[f(s[i - 1])] += 1;
res -= zero[f(s[i])] + nzero[f(s[i])];
}
else {
nzero[f(s[i - 1])] += 1;
res -= zero[f(s[i])];
}
}
std::cout << res + 1 << "\n";
return 0;
}
全部评论 10
顶
2025-02-27 来自 四川
1666,70多名都能加分.,
2025-03-06 来自 北京
0@像素君T4和T5看不了一点
2025-03-02 来自 北京
0有人吗?什么时候发呀
2025-03-02 来自 北京
0在发了
2025-03-02 来自 广东
02025-03-02 来自 北京
0
非常好的题,让我的代码旋转
2025-03-01 来自 浙江
0我就蹭个排位分
2025-02-27 来自 浙江
0+1
2025-02-28 来自 北京
0
6
2025-02-27 来自 江西
0666,牢师,你这也太懒了。
2025-02-27 来自 北京
0qp
2025-02-27 来自 广东
02025-02-27 来自 北京
0
有帮助,赞一个