推荐

GESP C++ 五级历年真题题解

_Separation 2026-5-17 20:29:29 21 浏览 0 点赞 0 收藏

GESP C++ 五级历年真题题解

1. 小杨的锻炼

题目大意

班级里有 nn 名同学,第 ii 名同学每隔 aia_i 天锻炼一次。某一天所有同学都一起锻炼了,求下一次所有同学再次一起锻炼至少要过多少天。

解题思路

所有同学再次同时锻炼的时间,必须同时是所有 aia_i 的倍数。

所以答案就是:

lcm(a1,a2,,an)\operatorname{lcm}(a_1,a_2,\cdots,a_n)

由于 ai50a_i \le 50,但多个数的最小公倍数可能比较大,记得使用 long long\text{long long} 防止溢出。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
LL gcd(LL a, LL b) {
    while (b) {
        LL t = a % b;
        a = b;
        b = t;
    }
    return a;
}
int n;
inline void solve() {
    cin >> n;
    LL Ans = 1;
    for (int i = 1; i <= n; i++) {
        LL x; cin >> x;
        LL g = gcd((LL)(Ans % x), x);
        Ans = Ans / g * x;
    }
    printf("%lld\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

2. 小杨的队列

题目大意

NN 名同学,每名同学有一个身高。老师依次点名若干名同学入队。每次新同学先站到队尾,然后队伍要按身高从低到高重新排序。问每次新同学加入后,最少需要交换几次位置。

解题思路

每次加入新同学前,队伍已经是有序的。

新同学站到队尾后,要移动到合适位置。需要越过所有比他高的同学。

由于允许交换任意两名同学,最少交换次数等于当前队伍中身高大于新同学的人数。

所以每次统计已经入队的同学中,有多少人的身高大于当前同学即可。

不过数据存在相同元素,所以还需要进行标记,hi2,147,483,647h_i \le 2,147,483,647 所以使用 map\text{map} 进行标记。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n, m;
LL h[N];
vector<int> q;
map<LL, int> mp;
inline void solve() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> h[i];
    cin >> m;
    for (int i = 1, x; i <= m; i++) {
        cin >> x;
        int Ans = 0;
        for (int id : q) {
            if (mp[h[id]] == 0 && h[id] > h[x]) Ans++;
            mp[h[id]] = 1;
        }
        printf("%d\n", Ans);
        q.push_back(x);
        mp.clear();
    }
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

3. 因数分解

题目大意

给定一个正整数 NN,输出它的质因数分解式。

例如:

20=22×520 = 2^2 \times 5

输出格式中乘号用 *\texttt{*} 表示,指数用 \texttt{^} 表示。

解题思路

22 开始枚举可能的质因数 ii

如果 ii 能整除当前的 NN,就不断除以 ii,统计 ii 出现了多少次。

最后如果剩下的 N>1N > 1,说明它本身是一个质因数。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
LL n;
vector<pair<LL, int> > v;
inline void solve() {
    cin >> n;
    for (LL i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            int cnt = 0;
            while (n % i == 0) {
                n /= i;
                cnt++;
            }
            v.push_back({i, cnt});
        }
    }
    if (n > 1) v.push_back({n, 1});
    for (int i = 0; i < (int)v.size(); i++) {
        if (i) printf(" * ");
        printf("%lld", v[i].first);
        if (v[i].second > 1) {
            printf("^%d", v[i].second);
        }
    }
    printf("\n");
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

4. 巧夺大奖

题目大意

nn 个小游戏和 nn 个时间段。每个小游戏需要 11 个时间段完成,第 ii 个小游戏必须在第 TiT_i 个时间段结束前完成,完成后获得奖励 RiR_i。求最多能获得多少奖励。

解题思路

这是经典的 带截止时间的单位任务调度

贪心策略:

优先考虑奖励高的游戏。

对于每个游戏,尽量安排在不超过截止时间的最晚空闲时间段。

这样可以给后面的游戏留下更靠前的时间段。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
struct Node {
    int t, r;
};
int n;
vector<Node> a;
int vis[N];
inline void solve() {
    cin >> n;
    a.resize(n);
    for (int i = 0; i < n; i++) cin >> a[i].t;
    for (int i = 0; i < n; i++) cin >> a[i].r;
    sort(a.begin(), a.end(), [](Node x, Node y) {
        return x.r > y.r;
    });
    int Ans = 0;
    for (int i = 0; i < n; i++) {
        for (int j = a[i].t; j >= 1; j--) {
            if (!vis[j]) {
                vis[j] = 1;
                Ans += a[i].r;
                break;
            }
        }
    }
    printf("%d\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

5. 小杨的幸运数

题目大意

所有大于等于 aa 的完全平方数都是超级幸运数。所有超级幸运数的倍数都是幸运数。

给定 NN 个数 xx,如果 xx 是幸运数,输出 lucky\text{lucky};否则输出不小于 xx 的最小幸运数。

解题思路

数据中 x1000001x \le 1000001

如果某个完全平方数 s2as^2 \ge a,那么它的所有倍数都是幸运数。

因此可以预处理:

  1. 枚举所有 s2as^2 \ge a 的平方数;
  2. 把它们的倍数标记为幸运数;
  3. 从后往前预处理 nxt[i]\text{nxt[i]},表示不小于 ii 的最小幸运数。

因为答案最多可能到 20000002000000 左右,所以预处理范围取到 20000052000005

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 2000010;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int a, n;
int ok[N], nxt[N];
inline void solve() {
    cin >> a >> n;
    int st = 1;
    while (st * st < a) st++;
    for (int i = st; 1LL * i * i < N; i++) {
        int s = i * i;
        for (int j = s; j < N; j += s) {
            ok[j] = 1;
        }
    }
    nxt[N - 1] = -1;
    for (int i = N - 2; i >= 1; i--) {
        if (ok[i]) nxt[i] = i;
        else nxt[i] = nxt[i + 1];
    }
    while (n--) {
        int x; cin >> x;
        if (ok[x]) printf("lucky\n");
        else printf("%d\n", nxt[x]);
    }
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

6. 烹饪问题

题目大意

NN 种食材,每种食材有美味度 aia_i。两种食材的契合度为:

ai & aja_i\ \&\ a_j

求任意两种不同食材之间的最大契合度。

解题思路

从高位到低位贪心构造答案。

假设当前答案为 ans\text{ans},尝试把某一位加入答案,得到 tmp\text{tmp}

如果至少有两个数 xx 满足:

(x & tmp)=tmp(x\ \&\ tmp) = tmp

说明可以找到两个数,它们的按位与至少包含 tmp\text{tmp} 的所有二进制位。

于是这一位可以保留。

复杂度为:

O(31N)O(31N)

可以通过 N106N \le 10^6

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n;
int a[N];
inline void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    int Ans = 0;
    for (int b = 30; b >= 0; b--) {
        int tmp = Ans | (1 << b);
        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            if ((a[i] & tmp) == tmp) {
                cnt++;
                if (cnt >= 2) break;
            }
        }
        if (cnt >= 2) Ans = tmp;
    }
    printf("%d\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

7. 成绩排序

题目大意

NN 名同学,每人有语文、数学、英语三科成绩。排名规则如下:

  1. 总分高者靠前;
  2. 总分相同,语文加数学高者靠前;
  3. 仍相同,语文和数学中的最高分高者靠前;
  4. 仍相同,则并列。

要求按输入顺序输出每名同学的排名。

解题思路

先把每个同学的排序关键字算出来:

sum=c+m+esum = c + m + e cm=c+mcm = c + m mx=max(c,m)mx = \max(c,m)

然后排序。

排名时,如果当前同学和前一个同学三个关键字都相同,则排名相同;否则排名为当前位置编号。

例如排序后第 ii 名从 11 开始,如果不并列,排名就是 ii

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
struct Stu {
    int id;
    int sum, cm, mx;
};
int n;
int rk[N];
vector<Stu> a;
bool same(Stu x, Stu y) {
    return x.sum == y.sum && x.cm == y.cm && x.mx == y.mx;
}
inline void solve() {
    cin >> n;
    a.resize(n);
    for (int i = 0, c, m, e; i < n; i++) {
        cin >> c >> m >> e;
        a[i].id = i;
        a[i].sum = c + m + e;
        a[i].cm = c + m;
        a[i].mx = max(c, m);
    }
    sort(a.begin(), a.end(), [](Stu x, Stu y) {
        if (x.sum != y.sum) return x.sum > y.sum;
        if (x.cm != y.cm) return x.cm > y.cm;
        if (x.mx != y.mx) return x.mx > y.mx;
        return x.id < y.id;
    });
    for (int i = 0; i < n; i++) {
        if (i > 0 && same(a[i], a[i - 1])) {
            rk[a[i].id] = rk[a[i - 1].id];
        } else {
            rk[a[i].id] = i + 1;
        }
    }
    for (int i = 0; i < n; i++) printf("%d\n", rk[i]);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

8. B-smooth 数

题目大意

如果一个正整数的最大质因子不超过 BB,则称它为 B-smooth 数。给定 n,Bn,B,求不超过 nn 的 B-smooth 数有多少个。

解题思路

预处理每个数的最大质因子。

可以用筛法:

如果 ii 是质数,那么它是所有 ii 的倍数的一个质因子。因为从小到大枚举质数,所以不断覆盖后,最后留下的就是最大质因子。

特别地,11 没有质因子,通常认为它满足条件。

最后统计最大质因子不超过 BB 的数即可。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n, b;
int mxp[N];
inline void solve() {
    cin >> n >> b;
    for (int i = 2; i <= n; i++) {
        if (mxp[i] == 0) {
            for (int j = i; j <= n; j += i) {
                mxp[j] = i;
            }
        }
    }
    int Ans = 1;
    for (int i = 2; i <= n; i++) {
        if (mxp[i] <= b) Ans++;
    }
    printf("%d\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

9. 黑白格

题目大意

给定一个 nnmm 列的黑白网格,1\texttt{1} 表示黑格。求至少包含 kk 个黑格的最小子矩形面积。如果不存在,输出 00

解题思路

枚举子矩形的上边界和下边界。

对于固定的上下边界,可以把每一列中黑格数量压缩成一维数组。

问题变成:

在一维数组中找一个最短连续区间,使得区间和至少为 kk

由于数组元素非负,可以用双指针维护。

复杂度:

O(n2m)O(n^2m)

当然也可以使用 前缀和 操作,这个是最直观的思考方式,只不过时间复杂的为 O(n4)O(n^4)

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10, inf = 1e9 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n, m, k;
string g[110];
inline void solve() {
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++) {
        cin >> g[i];
        g[i] = " " + g[i];
    }
    int Ans = inf;
    for (int up = 1; up <= n; up++) {
        vector<int> col(m + 1, 0);
        for (int down = up; down <= n; down++) {
            for (int j = 1; j <= m; j++) {
                if (g[down][j] == '1') col[j]++;
            }
            int l = 1;
            int sum = 0;
            for (int r = 1; r <= m; r++) {
                sum += col[r];
                while (l <= r && sum >= k) {
                    int area = (down - up + 1) * (r - l + 1);
                    Ans = min(Ans, area);
                    sum -= col[l];
                    l++;
                }
            }
        }
    }
    if (Ans == inf) printf("0\n");
    else printf("%d\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e3 + 10, inf = 1e9 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n, m, k, Sum[N][N];
string g[110];
inline void solve() {
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++) {
        cin >> g[i];
        g[i] = " " + g[i];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (g[i][j] == '1') Sum[i][j] = 1;
            else Sum[i][j] = 0;
            Sum[i][j] = Sum[i][j] + Sum[i - 1][j] + Sum[i][j - 1] - Sum[i - 1][j - 1];
        }
    }
    int Ans = inf;
    for (int x1 = 1; x1 <= n; x1++) {
        for (int y1 = 1; y1 <= m; y1++) {
            for (int x2 = x1; x2 <= n; x2++) {
                for (int y2 = y1; y2 <= m; y2++) {
                    int t = Sum[x2][y2] - Sum[x1 - 1][y2] - Sum[x2][y1 - 1] + Sum[x1 - 1][y1 - 1];
                    if (t >= k) Ans = min(Ans, ((x2 - x1 + 1) * (y2 - y1 + 1)));
                }
            }
        }
    }
    if (Ans == inf) printf("0\n");
    else printf("%d\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

10. 小杨的幸运数字

题目大意

如果一个正整数恰好有两种不同的质因子,则它是幸运数字。给定 nn 个正整数,判断每个数是否为幸运数字。

解题思路

数据范围中 ai106a_i \le 10^6

可以预处理每个数有多少种不同质因子。

筛法做法:

枚举每个质数 pp,给所有 pp 的倍数的质因子种类数加 11

最后判断 cnt[x] == 2\text{cnt[x] == 2} 即可。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n;
int cnt[N];
inline void init() {
    for (int i = 2; i < N; i++) {
        if (cnt[i] == 0) {
            for (int j = i; j < N; j += i) {
                cnt[j]++;
            }
        }
    }
}
inline void solve() {
    init();
    cin >> n;
    while (n--) {
        int x; cin >> x;
        if (cnt[x] == 2) printf("1\n");
        else printf("0\n");
    }
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

11. 挑战怪物

题目大意

怪物血量为 hh。小杨有两种攻击方式:

  1. ii 次物理攻击造成 2i12^{i-1} 点伤害;
  2. 魔法攻击可以选择一个不超过当前血量的质数 xx,造成 xx 点伤害,但最多只能用一次。

要求怪物血量恰好变成 00,求最少攻击次数,不能击败输出 1-1

解题思路

如果使用 kk 次物理攻击,物理总伤害为:

1+2+4++2k1=2k11+2+4+\cdots+2^{k-1}=2^k-1

如果不用魔法,则需要:

h=2k1h=2^k-1

如果使用一次魔法,则需要:

h(2k1)h-(2^k-1)

是一个质数。

枚举 kk,找到最小攻击次数即可。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10, inf = 1e9 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int h;
int pri[N];
inline void init() {
    for (int i = 2; i < N; i++) pri[i] = 1;
    for (int i = 2; i * i < N; i++) {
        if (pri[i]) {
            for (int j = i * i; j < N; j += i) {
                pri[j] = 0;
            }
        }
    }
}
inline void solve() {
    cin >> h;
    int Ans = inf;
    for (int k = 0; k <= 20; k++) {
        int s = (1 << k) - 1;
        if (s > h) break;
        if (s == h) {
            Ans = min(Ans, k);
        } else {
            int p = h - s;
            if (p >= 2 && pri[p]) {
                Ans = min(Ans, k + 1);
            }
        }
    }
    if (Ans == inf) printf("-1\n");
    else printf("%d\n", Ans);
}
int main() {
    init();
    int _ = 1; cin >> _;
    while (_--) solve();
    return 0;
}

12. 小杨的武器

题目大意

小杨有 nn 种武器,每种武器有初始熟练度 cic_i。之后有 mm 场战斗,第 jj 场战斗会让所选武器熟练度增加 aja_j,其中 aja_j 可能为负。每场必须选择一种武器。求最终所有武器熟练度最大值最大是多少。

解题思路

如果只有一种武器,那么所有变化都只能加到它身上。

如果至少有两种武器,那么:

  • 正数变化全部加到当前初始熟练度最高的武器上;
  • 非正数变化丢给其他武器承受。

所以答案为:

max(ci)+max(0,aj)\max(c_i)+\sum \max(0,a_j)

特殊情况 n=1n=1 时,答案为:

c1+ajc_1+\sum a_j

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const LL inf = 4e18 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n, m;
LL c[N], a[N];
inline void solve() {
    cin >> n >> m;
    LL mx = -inf;
    for (int i = 1; i <= n; i++) {
        cin >> c[i];
        mx = max(mx, c[i]);
    }
    LL sum = 0, pos = 0;
    for (int i = 1; i <= m; i++) {
        cin >> a[i];
        sum += a[i];
        if (a[i] > 0) pos += a[i];
    }
    if (n == 1) printf("%lld\n", c[1] + sum);
    else printf("%lld\n", mx + pos);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

13. 奇妙数字

题目大意

如果 x=pax=p^a,其中 pp 是质数,aa 是正整数,则 xx 是奇妙数字。

给定 nn,希望选出尽可能多的不重复奇妙数字,使它们的乘积是 nn 的因子。求最多能选多少个。

解题思路

先分解:

n=p1e1p2e2pkekn=p_1^{e_1}p_2^{e_2}\cdots p_k^{e_k}

对于某个质数 pp,可以选择:

p1,p2,p3,,ptp^1,p^2,p^3,\cdots,p^t

它们乘起来会消耗指数:

1+2+3++t=t(t+1)21+2+3+\cdots+t=\frac{t(t+1)}{2}

所以对于每个指数 eie_i,找最大的 tt 满足:

t(t+1)2ei\frac{t(t+1)}{2}\le e_i

最后把所有 tt 加起来。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
LL n;
inline void solve() {
    cin >> n;
    LL Ans = 0;
    for (LL i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            int e = 0;
            while (n % i == 0) {
                n /= i;
                e++;
            }
            int t = 0;
            while ((t + 1) * (t + 2) / 2 <= e) {
                t++;
            }
            Ans += t;
        }
    }
    if (n > 1) {
        Ans += 1;
    }
    printf("%lld\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

14. 武器强化

题目大意

nn 种武器和 mm 种强化材料。第 ii 种材料当前适配武器 pip_i,可以花费 cic_i 把它改为任意武器。小杨希望适配第 11 种武器的材料数量严格大于其他任意武器,求最少花费。

解题思路

枚举最终第 11 种武器有 tt 个材料。

此时其他每种武器最多只能有 t1t-1 个材料。

对于某个非 11 武器,如果它当前有 cntcnt 个材料:

  • cnttcnt \ge t,必须移动走 cnt(t1)cnt-(t-1) 个材料;
  • 为了花费最小,应移动该武器中修改费用最小的那些材料。

这些强制移动的材料都可以改到武器 11

如果强制移动后,武器 11 的数量还不到 tt,就从剩余材料中再选修改费用最小的若干个改到武器 11

枚举所有 tt,取最小花费。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const LL inf = 1LL << 62;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n, m;
vector<LL> v[1010];
inline void solve() {
    cin >> n >> m;
    int c1 = 0;
    for (int i = 1; i <= m; i++) {
        int p;
        LL c;
        cin >> p >> c;
        if (p == 1) c1++;
        else v[p].push_back(c);
    }
    for (int i = 2; i <= n; i++) {
        sort(v[i].begin(), v[i].end());
    }
    LL Ans = inf;
    for (int t = max(1, c1); t <= m; t++) {
        LL cost = 0;
        int have = c1;
        vector<LL> rest;
        for (int i = 2; i <= n; i++) {
            int sz = v[i].size();
            int need = max(0, sz - (t - 1));
            for (int j = 0; j < need; j++) {
                cost += v[i][j];
                have++;
            }
            for (int j = need; j < sz; j++) {
                rest.push_back(v[i][j]);
            }
        }
        if (have < t) {
            sort(rest.begin(), rest.end());
            int need = t - have;
            if ((int)rest.size() < need) continue;
            for (int i = 0; i < need; i++) {
                cost += rest[i];
            }
        }
        Ans = min(Ans, cost);
    }
    printf("%lld\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

15. 平均分配

题目大意

2n2n 件物品。第 ii 件卖给小 B 可以获得 bib_i,卖给小 C 可以获得 cic_i。要求小 B 和小 C 各买恰好 nn 件,求最大总收入。

解题思路

先假设所有物品都卖给小 C,初始收入为:

ci\sum c_i

如果第 ii 件改卖给小 B,收入变化为:

bicib_i-c_i

因此只需要选择 nn 个最大的 bicib_i-c_i 加到答案中即可。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n;
LL b[N], c[N], d[N];
inline void solve() {
    cin >> n;
    for (int i = 1; i <= 2 * n; i++) cin >> b[i];
    for (int i = 1; i <= 2 * n; i++) cin >> c[i];
    LL Ans = 0;
    for (int i = 1; i <= 2 * n; i++) {
        Ans += c[i];
        d[i] = b[i] - c[i];
    }
    sort(d + 1, d + 2 * n + 1, greater<LL>());
    for (int i = 1; i <= n; i++) {
        Ans += d[i];
    }
    printf("%lld\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

16. 原根判断

题目大意

给定多组 a,pa,p,其中 pp 是质数。判断 aa 是否是模 pp 意义下的原根。

解题思路

对于质数 pp,如果 aapp 的原根,那么 aa 在模 pp 下的阶应为 p1p-1

判断方法:

设:

φ(p)=p1\varphi(p)=p-1

分解 p1p-1 的所有不同质因子 qq

如果存在某个 qq 满足:

a(p1)/q1(modp)a^{(p-1)/q}\equiv 1 \pmod p

那么 aa 不是原根。

否则 aa 是原根。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
LL qpow(LL a, LL b, LL mod) {
    LL res = 1 % mod;
    a %= mod;
    while (b) {
        if (b & 1) res = (__int128)res * a % mod;
        a = (__int128)a * a % mod;
        b >>= 1;
    }
    return res;
}
LL gcd(LL a, LL b) {
    while (b) {
        LL t = a % b;
        a = b;
        b = t;
    }
    return a;
}
LL a, p;
inline void solve() {
    cin >> a >> p;
    if (gcd(a, p) != 1) {
        printf("No\n");
        return;
    }
    LL phi = p - 1;
    LL x = phi;
    vector<LL> fac;
    for (LL i = 2; i * i <= x; i++) {
        if (x % i == 0) {
            fac.push_back(i);
            while (x % i == 0) x /= i;
        }
    }
    if (x > 1) fac.push_back(x);
    bool ok = true;
    for (LL q : fac) {
        if (qpow(a, phi / q, p) == 1) {
            ok = false;
            break;
        }
    }
    if (ok) printf("Yes\n");
    else printf("No\n");
}
int main() {
    int _ = 1; cin >> _;
    while (_--) solve();
    return 0;
}

17. 奖品兑换

题目大意

小 A 有 nn 张课堂优秀券和 mm 张作业优秀券。兑换一份奖品有两种方式:

  1. aa 张课堂券和 bb 张作业券;
  2. bb 张课堂券和 aa 张作业券。

求最多能兑换多少份奖品。

解题思路

二分答案 kk,判断能否兑换 kk 份。

设其中 xx 份使用第一种方式,则 kxk-x 份使用第二种方式。

需要满足:

ax+b(kx)nax+b(k-x)\le n bx+a(kx)mbx+a(k-x)\le m

这两个不等式都会限制 xx 的取值范围。

只要存在整数 xx 满足:

0xk0\le x\le k

并同时满足两个不等式,就说明 kk 可行。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
LL n, m, a, b;
LL floordiv(LL x, LL y) {
    if (y < 0) x = -x, y = -y;
    if (x >= 0) return x / y;
    return -((-x + y - 1) / y);
}
LL ceildiv(LL x, LL y) {
    if (y < 0) x = -x, y = -y;
    if (x >= 0) return (x + y - 1) / y;
    return -((-x) / y);
}
bool add(LL c, LL r, LL &l, LL &rr) {
    if (c == 0) return r >= 0;
    if (c > 0) {
        rr = min(rr, floordiv(r, c));
    } else {
        l = max(l, ceildiv(r, c));
    }
    return l <= rr;
}
bool check(LL k) {
    if (a == b) {
        return a * k <= n && a * k <= m;
    }
    LL l = 0, r = k;
    LL d = a - b;
    if (!add(d, n - b * k, l, r)) return false;
    if (!add(-d, m - a * k, l, r)) return false;
    return l <= r;
}
inline void solve() {
    cin >> n >> m >> a >> b;
    LL l = 0, r = (n + m) / (a + b), Ans = 0;
    while (l <= r) {
        LL mid = (l + r) / 2;
        if (check(mid)) {
            Ans = mid;
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    printf("%lld\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

18. 最大公因数

题目大意

给定 nn 个正整数 a1,a2,,ana_1,a_2,\cdots,a_nqq 组询问。第 ii 组询问要求输出:

gcd(a1+i,a2+i,,an+i)\gcd(a_1+i,a_2+i,\cdots,a_n+i)

解题思路

利用性质:

gcd(x,y)=gcd(x,yx)\gcd(x,y)=\gcd(x,y-x)

所以:

gcd(a1+i,a2+i,,an+i)\gcd(a_1+i,a_2+i,\cdots,a_n+i)

等价于:

gcd(a1+i,a2a1,a3a1,,ana1)\gcd(a_1+i,a_2-a_1,a_3-a_1,\cdots,a_n-a_1)

先预处理:

g=gcd(a2a1,a3a1,,ana1)g=\gcd(|a_2-a_1|,|a_3-a_1|,\cdots,|a_n-a_1|)

每次询问答案就是:

gcd(a1+i,g)\gcd(a_1+i,g)

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
LL gcd(LL a, LL b) {
    if (a < 0) a = -a;
    if (b < 0) b = -b;
    while (b) {
        LL t = a % b;
        a = b;
        b = t;
    }
    return a;
}
int n, q;
LL a[N];
inline void solve() {
    cin >> n >> q;
    for (int i = 1; i <= n; i++) cin >> a[i];
    LL g = 0;
    for (int i = 2; i <= n; i++) g = gcd(g, a[i] - a[1]);
    for (int i = 1; i <= q; i++) printf("%lld\n", gcd(a[1] + i, g));
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

19. 数字选取

题目大意

给定 1,2,,n1,2,\ldots,nnn 个整数,从中选取尽可能多的整数,使得任意两个不同的整数互质。求最多能选多少个。

解题思路

除了 11 之外,每个大于 11 的数至少含有一个质因子。

如果两个数有相同质因子,它们就不互质。

因此选出的每个大于 11 的数都至少要占用一个不同的质因子,所以数量不会超过:

π(n)+1\pi(n)+1

其中 π(n)\pi(n) 表示不超过 nn 的质数个数。

这个上界可以达到:选择 11 和所有不超过 nn 的质数。

所以答案就是:

1+不超过 n 的质数个数1+\text{不超过 }n\text{ 的质数个数}

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n;
int vis[N];
inline void solve() {
    cin >> n;
    int cnt = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            cnt++;
            for (LL j = 1LL * i * i; j <= n; j += i) {
                vis[j] = 1;
            }
        }
    }
    printf("%d\n", cnt + 1);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

20. 有趣的数字和

题目大意

如果一个正整数的二进制表示中包含奇数个 1\texttt{1},则称它为有趣的。给定 l,rl,r,求区间 [l,r][l,r] 内所有有趣整数的和。

解题思路

求前缀函数:

F(n)=1n 中所有有趣整数的和F(n)=1\sim n\text{ 中所有有趣整数的和}

答案为:

F(r)F(l1)F(r)-F(l-1)

用二进制数位统计。

预处理长度为 lenlen 的二进制后缀中:

  • cnt[len][0/1]\text{cnt[len][0/1]}:有偶数/奇数个 1\texttt{1} 的数有多少个;
  • sum[len][0/1]\text{sum[len][0/1]}:这些数本身的和。

然后从高位到低位扫描 nn,统计所有不超过 nn1\texttt{1} 的数量为奇数的数的和。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
LL l, r;
LL cnt[40][2], sumv[40][2], pw[40];
void init() {
    pw[0] = 1;
    for (int i = 1; i < 40; i++) pw[i] = pw[i - 1] * 2;
    cnt[0][0] = 1;
    cnt[0][1] = 0;
    for (int len = 1; len < 40; len++) {
        for (int p = 0; p <= 1; p++) {
            cnt[len][p] = cnt[len - 1][p] + cnt[len - 1][p ^ 1];
            sumv[len][p] = sumv[len - 1][p] + sumv[len - 1][p ^ 1] + pw[len - 1] * cnt[len - 1][p ^ 1];
        }
    }
}
LL calc(LL n) {
    if (n <= 0) return 0;
    LL ans = 0;
    LL pre = 0;
    int par = 0;
    for (int b = 35; b >= 0; b--) {
        if ((n >> b) & 1) {
            int need = par ^ 1;
            ans += pre * cnt[b][need] + sumv[b][need];
            pre += 1LL << b;
            par ^= 1;
        }
    }
    if (par == 1) ans += n;
    return ans;
}
inline void solve() {
    init();
    cin >> l >> r;
    printf("%lld\n", calc(r) - calc(l - 1));
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

或者可以考虑打表观察,可以发现四位一组 4n,4n+1,4n+2,4n+34n, 4n + 1, 4n + 2, 4n + 3 这四个数前几位是固定的。

假如前面的二进位数字和是奇数,那么 4n4n4n+34n + 3 是有趣的;如果是前面的二进位数字和是偶数的话,那么 4n+14n + 14n+24n + 2 是有趣的;然后可以看到这四个值不管什么情况,有趣数字之和一定是 8n+38n + 3

假设 1n1 \sim nkk 组,那么就是 :

$$ \begin{align} \sum_{i = 0}^{k - 1}{8k} + \sum_{i = 0}^{k - 1}{3} &= 8 \times \frac{(k - 1) \times k}{2} + 3 \times k \\ &= 4k^2 - k \end{align} $$

很明显不可能真的是完整的 kk 组,所以多余的部分需要单独计算一下,4k+r=n,(0r<4)4k + r = n, (0 \le r < 4)

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
LL l, r;
LL ck(int x) {
    LL Res = 0;
    while (x) {
        if (x & 1) Res++;
        x >>= 1;
    }
    return Res;
}
LL f(int x) {
    LL t = x / 4;
    LL Sum = (t * t) * 4 - t;
    for (LL i = (t * 4); i <= x; i++) {
        if (ck(i) & 1) Sum += i;
    }
    return Sum;
}
inline void solve() {
    cin >> l >> r;
    printf("%lld\n", f(r) - f(l - 1));
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

21. 数字移动

题目大意

给定一个长度为 NN 的序列,其中每个数字恰好出现两次。每次可以选择一个数移动到任意位置,代价为这个数本身。要求最终每对相同数字都相邻。求最小的 xx,使得可以在每次移动代价不超过 xx 的情况下完成目标。

解题思路

如果限制每次移动的数字都不超过 xx,那么所有大于 xx 的数字都不能移动。

把所有不超过 xx 的数字删掉后,剩下的数字必须已经是:

a a b b c c ...

这种成对相邻的形式。

否则某个大数需要移动,无法完成。

因此可以二分 xx

  • 检查时保留所有 >x> x 的数字;
  • 判断剩余序列是否两两相等。

这个性质具有单调性:xx 越大,能移动的数越多,越容易满足。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n;
int a[N];
bool check(int x) {
    vector<int> v;
    for (int i = 1; i <= n; i++) {
        if (a[i] > x) v.push_back(a[i]);
    }
    if (v.size() % 2) return false;
    for (int i = 0; i < (int)v.size(); i += 2) {
        if (v[i] != v[i + 1]) return false;
    }
    return true;
}
inline void solve() {
    cin >> n;
    int mx = 0;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        mx = max(mx, a[i]);
    }
    int l = 0, r = mx, Ans = mx;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (check(mid)) {
            Ans = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    printf("%d\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

22. 相等序列

题目大意

给定 NN 个正整数。每次操作可以选择一个数:

  1. 乘以任意质数;
  2. 或者在能整除的情况下除以任意质数。

每次操作花费 11。求让所有数相等的最小花费。

解题思路

把每个数分解质因数。

对某个质数 pp,设它在所有数中的指数分别是:

e1,e2,,eNe_1,e_2,\cdots,e_N

一次乘以 pp,就是让某个指数 +1+1;一次除以 pp,就是让某个指数 1-1

要让所有数最终相同,就要把这些指数变成同一个值 tt

总代价为:

eit\sum |e_i-t|

这个值在 tt 取中位数时最小。

所以对每个质数独立处理,求指数的中位数代价,最后求和。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n;
int sp[N];
vector<int> e[N];
void init() {
    for (int i = 2; i < N; i++) {
        if (!sp[i]) {
            for (int j = i; j < N; j += i) {
                if (!sp[j]) sp[j] = i;
            }
        }
    }
}
inline void solve() {
    init();
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        while (x > 1) {
            int p = sp[x];
            int cnt = 0;
            while (x % p == 0) {
                x /= p;
                cnt++;
            }
            e[p].push_back(cnt);
        }
    }
    LL Ans = 0;
    for (int p = 2; p < N; p++) {
        if (e[p].empty()) continue;
        sort(e[p].begin(), e[p].end());
        int k = e[p].size();
        int zero = n - k;
        int mid = (n - 1) / 2;
        int t;
        if (mid < zero) t = 0;
        else t = e[p][mid - zero];
        Ans += 1LL * zero * t;
        for (int x : e[p]) {
            Ans += abs(x - t);
        }
    }
    printf("%lld\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

23. 有限不循环小数

题目大意

如果 1a\frac{1}{a} 可以化为有限不循环小数,则称 aa 为终止数。给定 L,RL,R,求区间 [L,R][L,R] 中终止数的数量。

解题思路

一个分数能化为有限小数,当且仅当化简后分母的质因子只包含 2255

这里是:

1a\frac{1}{a}

所以只需要判断 aa 的质因子是否只包含 2255

判断方法:

不断除以 22,再不断除以 55

最后如果变成 11,就是终止数。

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int l, r;
bool ok(int x) {
    while (x % 2 == 0) x /= 2;
    while (x % 5 == 0) x /= 5;
    return x == 1;
}
inline void solve() {
    cin >> l >> r;
    int Ans = 0;
    for (int i = l; i <= r; i++) {
        if (ok(i)) Ans++;
    }
    printf("%d\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

24. 找数

题目大意

给定两个数组 AABB,两个数组内部元素都互不相同。求有多少个数同时出现在数组 AA 和数组 BB 中。

解题思路

set\text{set}\text{unordered_set} 记录数组 AA 中出现的数。

然后遍历数组 BB,如果某个数在集合中出现过,答案加 11

复杂度:

O(n+m)O(n+m)

代码

#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
    int x = 0,f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n, m;
inline void solve() {
    cin >> n >> m;
    unordered_set<LL> st;
    for (int i = 1; i <= n; i++) {
        LL x; cin >> x;
        st.insert(x);
    }
    int Ans = 0;
    for (int i = 1; i <= m; i++) {
        LL x; cin >> x;
        if (st.count(x)) Ans++;
    }
    printf("%lld\n", Ans);
}
int main() {
    int _ = 1;
    while (_--) solve();
    return 0;
}

评论

0 条
还没有评论。