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

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 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. 小杨的队列
题目大意
有 名同学,每名同学有一个身高。老师依次点名若干名同学入队。每次新同学先站到队尾,然后队伍要按身高从低到高重新排序。问每次新同学加入后,最少需要交换几次位置。
解题思路
每次加入新同学前,队伍已经是有序的。
新同学站到队尾后,要移动到合适位置。需要越过所有比他高的同学。
由于允许交换任意两名同学,最少交换次数等于当前队伍中身高大于新同学的人数。
所以每次统计已经入队的同学中,有多少人的身高大于当前同学即可。
不过数据存在相同元素,所以还需要进行标记, 所以使用 进行标记。
代码
#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. 因数分解
题目大意
给定一个正整数 ,输出它的质因数分解式。
例如:
输出格式中乘号用 表示,指数用 \texttt{^} 表示。
解题思路
从 开始枚举可能的质因数 。
如果 能整除当前的 ,就不断除以 ,统计 出现了多少次。
最后如果剩下的 ,说明它本身是一个质因数。
代码
#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. 巧夺大奖
题目大意
有 个小游戏和 个时间段。每个小游戏需要 个时间段完成,第 个小游戏必须在第 个时间段结束前完成,完成后获得奖励 。求最多能获得多少奖励。
解题思路
这是经典的 带截止时间的单位任务调度 。
贪心策略:
优先考虑奖励高的游戏。
对于每个游戏,尽量安排在不超过截止时间的最晚空闲时间段。
这样可以给后面的游戏留下更靠前的时间段。
代码
#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. 小杨的幸运数
题目大意
所有大于等于 的完全平方数都是超级幸运数。所有超级幸运数的倍数都是幸运数。
给定 个数 ,如果 是幸运数,输出 ;否则输出不小于 的最小幸运数。
解题思路
数据中 。
如果某个完全平方数 ,那么它的所有倍数都是幸运数。
因此可以预处理:
- 枚举所有 的平方数;
- 把它们的倍数标记为幸运数;
- 从后往前预处理 ,表示不小于 的最小幸运数。
因为答案最多可能到 左右,所以预处理范围取到 。
代码
#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. 烹饪问题
题目大意
有 种食材,每种食材有美味度 。两种食材的契合度为:
求任意两种不同食材之间的最大契合度。
解题思路
从高位到低位贪心构造答案。
假设当前答案为 ,尝试把某一位加入答案,得到 。
如果至少有两个数 满足:
说明可以找到两个数,它们的按位与至少包含 的所有二进制位。
于是这一位可以保留。
复杂度为:
可以通过 。
代码
#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. 成绩排序
题目大意
有 名同学,每人有语文、数学、英语三科成绩。排名规则如下:
- 总分高者靠前;
- 总分相同,语文加数学高者靠前;
- 仍相同,语文和数学中的最高分高者靠前;
- 仍相同,则并列。
要求按输入顺序输出每名同学的排名。
解题思路
先把每个同学的排序关键字算出来:
然后排序。
排名时,如果当前同学和前一个同学三个关键字都相同,则排名相同;否则排名为当前位置编号。
例如排序后第 名从 开始,如果不并列,排名就是 。
代码
#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 数
题目大意
如果一个正整数的最大质因子不超过 ,则称它为 B-smooth 数。给定 ,求不超过 的 B-smooth 数有多少个。
解题思路
预处理每个数的最大质因子。
可以用筛法:
如果 是质数,那么它是所有 的倍数的一个质因子。因为从小到大枚举质数,所以不断覆盖后,最后留下的就是最大质因子。
特别地, 没有质因子,通常认为它满足条件。
最后统计最大质因子不超过 的数即可。
代码
#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. 黑白格
题目大意
给定一个 行 列的黑白网格, 表示黑格。求至少包含 个黑格的最小子矩形面积。如果不存在,输出 。
解题思路
枚举子矩形的上边界和下边界。
对于固定的上下边界,可以把每一列中黑格数量压缩成一维数组。
问题变成:
在一维数组中找一个最短连续区间,使得区间和至少为 。
由于数组元素非负,可以用双指针维护。
复杂度:
当然也可以使用 前缀和 操作,这个是最直观的思考方式,只不过时间复杂的为 。
代码
#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. 小杨的幸运数字
题目大意
如果一个正整数恰好有两种不同的质因子,则它是幸运数字。给定 个正整数,判断每个数是否为幸运数字。
解题思路
数据范围中 。
可以预处理每个数有多少种不同质因子。
筛法做法:
枚举每个质数 ,给所有 的倍数的质因子种类数加 。
最后判断 即可。
代码
#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. 挑战怪物
题目大意
怪物血量为 。小杨有两种攻击方式:
- 第 次物理攻击造成 点伤害;
- 魔法攻击可以选择一个不超过当前血量的质数 ,造成 点伤害,但最多只能用一次。
要求怪物血量恰好变成 ,求最少攻击次数,不能击败输出 。
解题思路
如果使用 次物理攻击,物理总伤害为:
如果不用魔法,则需要:
如果使用一次魔法,则需要:
是一个质数。
枚举 ,找到最小攻击次数即可。
代码
#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. 小杨的武器
题目大意
小杨有 种武器,每种武器有初始熟练度 。之后有 场战斗,第 场战斗会让所选武器熟练度增加 ,其中 可能为负。每场必须选择一种武器。求最终所有武器熟练度最大值最大是多少。
解题思路
如果只有一种武器,那么所有变化都只能加到它身上。
如果至少有两种武器,那么:
- 正数变化全部加到当前初始熟练度最高的武器上;
- 非正数变化丢给其他武器承受。
所以答案为:
特殊情况 时,答案为:
代码
#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. 奇妙数字
题目大意
如果 ,其中 是质数, 是正整数,则 是奇妙数字。
给定 ,希望选出尽可能多的不重复奇妙数字,使它们的乘积是 的因子。求最多能选多少个。
解题思路
先分解:
对于某个质数 ,可以选择:
它们乘起来会消耗指数:
所以对于每个指数 ,找最大的 满足:
最后把所有 加起来。
代码
#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. 武器强化
题目大意
有 种武器和 种强化材料。第 种材料当前适配武器 ,可以花费 把它改为任意武器。小杨希望适配第 种武器的材料数量严格大于其他任意武器,求最少花费。
解题思路
枚举最终第 种武器有 个材料。
此时其他每种武器最多只能有 个材料。
对于某个非 武器,如果它当前有 个材料:
- 若 ,必须移动走 个材料;
- 为了花费最小,应移动该武器中修改费用最小的那些材料。
这些强制移动的材料都可以改到武器 。
如果强制移动后,武器 的数量还不到 ,就从剩余材料中再选修改费用最小的若干个改到武器 。
枚举所有 ,取最小花费。
代码
#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. 平均分配
题目大意
有 件物品。第 件卖给小 B 可以获得 ,卖给小 C 可以获得 。要求小 B 和小 C 各买恰好 件,求最大总收入。
解题思路
先假设所有物品都卖给小 C,初始收入为:
如果第 件改卖给小 B,收入变化为:
因此只需要选择 个最大的 加到答案中即可。
代码
#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. 原根判断
题目大意
给定多组 ,其中 是质数。判断 是否是模 意义下的原根。
解题思路
对于质数 ,如果 是 的原根,那么 在模 下的阶应为 。
判断方法:
设:
分解 的所有不同质因子 。
如果存在某个 满足:
那么 不是原根。
否则 是原根。
代码
#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 有 张课堂优秀券和 张作业优秀券。兑换一份奖品有两种方式:
- 用 张课堂券和 张作业券;
- 用 张课堂券和 张作业券。
求最多能兑换多少份奖品。
解题思路
二分答案 ,判断能否兑换 份。
设其中 份使用第一种方式,则 份使用第二种方式。
需要满足:
这两个不等式都会限制 的取值范围。
只要存在整数 满足:
并同时满足两个不等式,就说明 可行。
代码
#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. 最大公因数
题目大意
给定 个正整数 和 组询问。第 组询问要求输出:
解题思路
利用性质:
所以:
等价于:
先预处理:
每次询问答案就是:
代码
#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. 数字选取
题目大意
给定 共 个整数,从中选取尽可能多的整数,使得任意两个不同的整数互质。求最多能选多少个。
解题思路
除了 之外,每个大于 的数至少含有一个质因子。
如果两个数有相同质因子,它们就不互质。
因此选出的每个大于 的数都至少要占用一个不同的质因子,所以数量不会超过:
其中 表示不超过 的质数个数。
这个上界可以达到:选择 和所有不超过 的质数。
所以答案就是:
代码
#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. 有趣的数字和
题目大意
如果一个正整数的二进制表示中包含奇数个 ,则称它为有趣的。给定 ,求区间 内所有有趣整数的和。
解题思路
求前缀函数:
答案为:
用二进制数位统计。
预处理长度为 的二进制后缀中:
- :有偶数/奇数个 的数有多少个;
- :这些数本身的和。
然后从高位到低位扫描 ,统计所有不超过 且 的数量为奇数的数的和。
代码
#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;
}
或者可以考虑打表观察,可以发现四位一组 这四个数前几位是固定的。
假如前面的二进位数字和是奇数,那么 和 是有趣的;如果是前面的二进位数字和是偶数的话,那么 和 是有趣的;然后可以看到这四个值不管什么情况,有趣数字之和一定是 。
假设 有 组,那么就是 :
$$ \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} $$很明显不可能真的是完整的 组,所以多余的部分需要单独计算一下, 。
#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. 数字移动
题目大意
给定一个长度为 的序列,其中每个数字恰好出现两次。每次可以选择一个数移动到任意位置,代价为这个数本身。要求最终每对相同数字都相邻。求最小的 ,使得可以在每次移动代价不超过 的情况下完成目标。
解题思路
如果限制每次移动的数字都不超过 ,那么所有大于 的数字都不能移动。
把所有不超过 的数字删掉后,剩下的数字必须已经是:
a a b b c c ...
这种成对相邻的形式。
否则某个大数需要移动,无法完成。
因此可以二分 :
- 检查时保留所有 的数字;
- 判断剩余序列是否两两相等。
这个性质具有单调性: 越大,能移动的数越多,越容易满足。
代码
#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. 相等序列
题目大意
给定 个正整数。每次操作可以选择一个数:
- 乘以任意质数;
- 或者在能整除的情况下除以任意质数。
每次操作花费 。求让所有数相等的最小花费。
解题思路
把每个数分解质因数。
对某个质数 ,设它在所有数中的指数分别是:
一次乘以 ,就是让某个指数 ;一次除以 ,就是让某个指数 。
要让所有数最终相同,就要把这些指数变成同一个值 。
总代价为:
这个值在 取中位数时最小。
所以对每个质数独立处理,求指数的中位数代价,最后求和。
代码
#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. 有限不循环小数
题目大意
如果 可以化为有限不循环小数,则称 为终止数。给定 ,求区间 中终止数的数量。
解题思路
一个分数能化为有限小数,当且仅当化简后分母的质因子只包含 和 。
这里是:
所以只需要判断 的质因子是否只包含 和 。
判断方法:
不断除以 ,再不断除以 。
最后如果变成 ,就是终止数。
代码
#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. 找数
题目大意
给定两个数组 和 ,两个数组内部元素都互不相同。求有多少个数同时出现在数组 和数组 中。
解题思路
用 或 \text{unordered_set} 记录数组 中出现的数。
然后遍历数组 ,如果某个数在集合中出现过,答案加 。
复杂度:
代码
#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;
}
京公网安备11010802045784号