官方题解|巅峰赛#17
2025-02-05 20:30:32
发布于:北京
ACGO 巅峰赛#17 题解
本场巅峰赛的所有题目的难度评级为:
| 题目编号 | 题目标题 | 难度 | 
|---|---|---|
| A. | 纪念品商店 | 入门 | 
| B. | 删除元素使数组去重 | 入门 | 
| C. | 摩天轮 | 普及- | 
| D. | 线性探查法 | 普及 | 
| E. | 统计 acgo 出现的次数 | 普及 | 
| F. | 美丽数 II | 普及 | 
| G. | 数的集合 | 普及+/提高 | 
| H. | 巨大的表格 | 提高+/省选- | 
题解简述
以下提供每道题目的思路简述,详细题解的AC代码包括复杂度分析,请点击题目标题查看。
A - 纪念品商店
模拟;数学
我们可以实现一个函数 int clamp(v, lo, hi)。
这个函数会返回 区间内,距离 最近的点。
那么显然,如果 在 内,函数返回 ;若不在区间内,则有两种情况:
- ,此时显然返回 ;
 - ,此时显然返回 ;
 
有了这个函数我们就可以判断 内离 最近的点和 的距离是否小于等于 ,从而得出答案。
- 在 
C++17此函数已加入标准库,定义在头文件<algorithm>中。 
#include <bits/stdc++.h>
int clamp(int v, int lo, int hi) {
    return v < lo ? lo : v > hi ? hi : v;
}
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int a, b, c, d;
    std::cin >> a >> b >> c >> d;
    int x = std::abs(clamp(c, a, b) - c);
    std::cout << (x <= d ? "Yes" : "No") << '\n';
    return 0;
}
B - 删除元素使数组去重
模拟
本题数据范围比较小,做法很多。这里使用一种时间复杂度较小,且通用的方法。
我们从后到前考虑整个数组中的元素,不断将元素添加至集合中,直至某个元素已经在集合中出现过。
此时数组中剩下的未被遍历的元素数量即为最小操作次数。
此方法时间复杂度 。
#include <bits/stdc++.h>
int main() {
    int n; std::cin >> n;
    std::vector<int> a(n);
    for (auto &x : a) std::cin >> x;
    std::set<int> st;
    while (!a.empty() and !st.count(a.back())) {
        st.insert(a.back());
        a.pop_back();
    }
    std::cout << a.size() << '\n';
    return 0;
}
C - 摩天轮
模拟;数学
对于任意时刻 ,我们考虑小码君的位置为 ,小码酱的位置为 ,那么二者之间的俯角或仰角度数为:
我们假定摩天轮的位置都是 ,然后根据计算出的位置,加上原来的位置,就可以得出实际的位置。
#include <bits/stdc++.h>
const double PI = std::acos(-1);
auto calc(double r, double p) {
    return std::make_pair(r * std::cos(p), r * std::sin(p));
}
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int t; std::cin >> t;
    int xa, ya, la, xb, yb, lb;
    std::cin >> xa >> ya >> la;
    std::cin >> xb >> yb >> lb;
    int q; std::cin >> q;
    while (q--) {
        int s; std::cin >> s;
        auto [y0, z0] = calc(la / 2.0, (1.0 * s / t - 0.25) * PI * 2);
        auto [y1, z1] = calc(lb / 2.0, (1.0 * s / t - 0.25) * PI * 2);
        y0 = -y0 + ya;
        y1 = -y1 + yb;
        z0 += la / 2.0;
        z1 += lb / 2.0;
        double res = std::atan2(std::abs(z0 - z1), std::hypot(xa - xb, y0 - y1)) * 180 / PI;
        std::cout << std::setprecision(9) << std::fixed << res << '\n';
    }
    return 0;
}
D - 线性探查法
数据结构(哈希表,平衡树)
我们考虑维护所有的为空的单元;对于「线性探测法」本质上就是找到 第一个大于等于 的空单元,若不存在这样的空单元,则寻找 第一个大于等于 的空单元。
那么我们可以考虑使用 std::set 来维护所有的  个空单元,并使用 std::map 来记录所有已经插入哈希表的元素分配到的位置即可。
时间复杂度:。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
    int n, q;
    std::cin >> n >> q;
    std::set<int> a;
    std::map<i64, int> mp;
    for (int i = 0; i < n; ++i)
        a.insert(i);
    while (q--) {
        i64 x; std::cin >> x;
        if (!mp.count(x)) {
            auto p = a.lower_bound(x % n);
            if (p == a.end()) p = a.lower_bound(0);
            mp[x] = *p;
            a.erase(p);
        }
        std::cout << mp[x] << '\n';
    }
    return 0;
}
E - 统计 acgo 出现的次数
动态规划;
我们考虑使用动态规划来统计所有的以 a,c,g,o 结尾的子序列的数量。
令  分别表示 a,c,g,o 结尾的子序列的数量。
遍历字符串 ,对于字符串  中的每个字符 ,若其为 a,则将  加一;若其为 b,则将  更新为 ; 若其为 c,则将  更新为 ; 若其为 d,则将  更新为 ; 若为其他字符,则不做任何处理。
最终 即为所求的答案。
#include <bits/stdc++.h>
const std::string S = "#acgo";
constexpr int MOD = 998244353;
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int n; std::cin >> n;
    std::string s; std::cin >> s;
    std::vector<int> dp(S.size());
    dp[0] = 1;
    for (auto &c : s) {
        auto p = S.find(c);
        if (p == std::string::npos) continue;
        dp[p] = (dp[p] + dp[p - 1]) % MOD;
    }
    std::cout << dp.back() << '\n';
    return 0;
}
F - 美丽数 II
质因子分解;
我们可以把 内所有的 个素数预处理,来加速质因数分解,使其复杂度降为 。这样整个算法时间复杂度为 ;其计算量大约为 ,可以在时限内通过题目。
#include <bits/stdc++.h>
 
using i64 = long long;
constexpr int N = 1e5;
std::bitset<N + 1> st;
std::vector<int> primes;
auto sieve = []() {
    for (i64 i = 2; i <= N; ++i) {
        if (st[i]) continue;
        for (i64 j = i * i; j <= N; j += i)
            st[j] = 1;
        primes.push_back(i);
    }
    return 0;
} ();
int solve() {
    i64 n; std::cin >> n;
    int cnt = 0;
    for (auto &p: primes) {
        while (n % p == 0) {
            n /= p;
            cnt += 1;
        }
        if (cnt > 3) break;
    }
    if (n > 1) cnt += 1;
    std::cout << (cnt == 3 ? "Yes" : "No") << '\n';
    return 0;   
}
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int _; std::cin >> _;
    while (_--) solve();
    return 0;
}
G - 数的集合
质数筛;并查集;前缀和
本题有两种方法可以解决,前者的切入点更平滑,后者更偏向于数学直觉一些。
方法一(并查集)
比较直接的一个想法,从 开始执行搜索,把 中未加入集合且与当前元素不互质的元素加入集合 。
那么显然我们的目标就是如何快速地找到与 不互质的元素集合 ,对于 中的每个元素,再找到与其不互质的元素,以此类推。
由于本题中的操作具有传递性质: 与 不互质; 与 不互质,那么 ,, 都属于同一个集合。
我们可以从小到大考虑每个元素 ,令 的最小质因子 ,那么有:
- 若 为质数,显然集合中只有 本身。
 - 若 不为质数,那么显然我们将其拆分为 与 ,那么二者所在的集合可以借助 合并起来。
 
我们将处理到元素 时,其所在的集合的大小设为 ,那么对应的操作次数为 。
所以我们可以预处理 中的所有元素,然后依次回答每个查询即可。
令 ,此方法时间复杂度为 。
并查集每个操作平均时间复杂度为 ,其中 为阿克曼函数的反函数,其增长极其缓慢,也就是说其单次操作的平均运行时间可以认为是一个很小的常数。
方法二详情点击标题链接查看。
#include <bits/stdc++.h>
// class: UnionFind
constexpr int N = 3e6;
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int st[N + 1]{};
    for (int i = 2; i * i <= N; ++i) {
        if (st[i]) continue;
        for (int j = i * i; j <= N; j += i)
            st[j] = i;
    }
    UnionFind d(N + 1);
    int res[N + 1]{};
    for (int i = 2; i <= N; ++i) {
        if (st[i]) {
            d.unite(i, st[i]);
            d.unite(i, i / st[i]);
        }
        res[i] = d.size(i) - 1;
    }
    int q; std::cin >> q;
    while (q--) {
        int n; std::cin >> n;
        std::cout << res[n] << '\n';
    }   
    return 0;
}
H - 巨大的表格
递推;矩阵快速幂
我们可以先计算出 列的所有元素的值,然后考虑表格中每一列元素的值,可以发现,对于 列的 个元素的值,可以完全由 列的元素得到:
由于 号元素每次加 ,所以我们可以考虑在最后给表格加一行全为 的元素作为 行上的元素,接着可以把转移写成矩阵的形式。
那么我们有:
由于 很大,对于每个查询 ,我们可以使用矩阵快速幂来计算出表格中 上的所有元素。
整个算法时间复杂度:。
#include <bits/stdc++.h>
// namespace: Matrix
// class: Modint
using mint = Modint<998244353>;
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int n, m, s, t;
    std::cin >> n >> m >> s >> t;
    Matrix::Mat<mint> dp = {std::vector<mint>(n + 1)};
    dp[0][0] = 1;
    for (int i = 1; i < n; ++i) {
        int x; std::cin >> x;
        dp[0][i] = dp[0][i - 1] * s + 1LL * x * t;
    }
    dp[0][n] = 1;
    Matrix::Mat<mint> a = Matrix::e<mint>(n + 1);
    a[n][0] = 1;
    for (int i = 1; i < n; ++i) {
        a[i][i] = t;
        for (int j = i - 1; j >= 0; --j)
            a[j][i] = a[j + 1][i] * s;
        a[0][i] /= t;
        a[n][i] = a[n][i - 1] * s;
    }
    int q; std::cin >> q;
    while (q--) {
        int r, c;
        std::cin >> r >> c;
        auto res = Matrix::multi(dp, Matrix::pow(a, c - 1));
        std::cout << res[0][r].val() << '\n';
    }
    return 0;
}
全部评论 3
666建议C题给个公式 不然谁的击败知道怎么写
2025-02-05 来自 浙江
1直言不讳(
2025-02-05 来自 广东
0C题给了公式呀
2025-02-05 来自 北京
0我说的是 这一坨石
2025-02-05 来自 浙江
0
std::cin.tie(nullptr)->sync_with_stdio(false);啥意思?
2025-02-11 来自 北京
02025-02-11 来自 北京
0取消输入同步流,可以让
cin变得和scanf一样快,副作用是不能同时使用cin和scanf,getchar等2025-02-11 来自 广东
0
顶
2025-02-04 来自 广东
0



















有帮助,赞一个