2025寒假算法练习——Week 3

文章目录

习题均来自 NEFU OJ

Problem 8 | 二倍的问题

  • Description

给定2到15个不同的正整数,你的任务是计算这些数里面有多少个数对满足:数对中一个数是另一个数的两倍。比如给定1 4 3 2 9 7 18 22,得到的答案是3,因为2是1的两倍,4是2个两倍,18是9的两倍。

  • Input

输入包括n组测试数据。每组数据包括一行,给出2到15个两两不同且小于100的正整数。每一行最后一个数是0,表示这一行的结束后,这个数不属于那2到15个给定的正整数。

  • Output

对每组输入数据,输出一行,给出有多少个数对满足其中一个数是另一个数的两倍。

  • Sample Input
13
21 4 3 2 9 7 18 22 0
32 4 8 10 0
47 5 11 13 1 3 0
  • Sample Output
13
22
30

简单题。唯一奇怪的是使用 bitset 比直接用 int* 的内存开销大。

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4#include <cstring>
 5
 6using namespace std;
 7
 8int max_value, i, value, n, cnt;
 9int values[100];
10
11int main() {
12    scanf("%d", &n);
13    while (n--) {
14        memset(values, 0, sizeof(values));
15        max_value = -1;
16        cnt = 0;
17
18        while (scanf("%d", &value) && value != 0) {
19            values[value] = 1;
20            if (value > max_value) {
21                max_value = value;
22            }
23        }
24
25        for (i = 1; i <= max_value / 2; i++) {
26            if (values[i] && values[i * 2]) {
27                cnt++;
28            }
29        }
30
31        printf("%d\n", cnt);
32    }
33    return 0;
34}

Problem 573 | 大乐透

  • Description

在小明曾经玩过的一种对号码的纸牌游戏(乐透)里,玩家必须从{1,2,……,49}中选择6个数。玩Lotto的一个流行策略是(虽然它并不增加你赢的机会):就是从这49个数中,选出k(k>6)个数组成一个子集S,然后只从S里拿出牌来玩几局游戏。例如,k=8,s={1,2,3,5,8,13,21,34},那么有28场可能的游戏:[1,2,3,5,8,13],[1,2,3,5,8,21],[1,2,3,5,8,34],[1,2,3,5,13,21],……,[3,5,8,13,21,24]。 读取数字k和一组数S,输出由S中的数组成的所有可能的游戏。

  • Input

输入数据有多组,每组一行,每行有多个整数,其中第一个整数为数字k,接下来有k个整数,即子集S。当k为0,输入结束。

  • Output

输出由S中的数组成的所有可能的游戏。每种游戏一行。

  • Sample Input
17 1 2 3 4 5 6 7
20
  • Sample Output
11 2 3 4 5 6
21 2 3 4 5 7
31 2 3 4 6 7
41 2 3 5 6 7
51 2 4 5 6 7
61 3 4 5 6 7
72 3 4 5 6 7

用递归(深搜)解决:

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4#include <cstring>
 5#include <algorithm>
 6
 7using namespace std;
 8
 9int nums[50], k;
10
11void solve(int* nums, int start, int depth, int* combination) {
12    if (depth == 6) {
13        printf("%d %d %d %d %d %d\n", combination[0], combination[1], combination[2], combination[3], combination[4], combination[5]);
14        return;
15    }
16
17    for (int i = start; i < k; i++) {
18        combination[depth] = nums[i];
19        solve(nums, i + 1, depth + 1, combination);
20    }
21}
22
23int main() {
24    while (scanf("%d", &k) != -1) {
25        if (k == 0) {
26            break;
27        }
28
29        memset(nums, 0, sizeof(nums));
30        for (int i = 0; i < k; i++) {
31            scanf("%d", &nums[i]);
32        }
33
34        sort(nums, nums + k);
35
36        int combination[6];
37        solve(nums, 0, 0, combination);
38    }
39
40    return 0;
41}

或者暴力循环嵌套:

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4#include <cstring>
 5#include <algorithm>
 6
 7using namespace std;
 8
 9int i, nums[50], k;
10int a, b, c, d, e, f;
11
12int main() {
13    while (scanf("%d", &k) != -1 && k != 0)
14    {
15        memset(nums, 0, sizeof(nums));
16        for (i = 0; i < k; i++) {
17            scanf("%d", &nums[i]);
18        }
19
20        sort(nums, nums + k);
21        for (a = 0; a < k - 5; a++) {
22            for (b = a + 1; b < k - 4; b++) {
23                for (c = b + 1; c < k - 3; c++) {
24                    for (d = c + 1; d < k - 2; d++) {
25                        for (e = d + 1; e < k - 1; e++) {
26                            for (f = e + 1; f < k; f++) {
27                                printf("%d %d %d %d %d %d\n", nums[a], nums[b], nums[c], nums[d], nums[e], nums[f]);
28                            }
29                        }
30                    }
31                }
32            }
33        }
34    }
35
36    return 0;
37}

使用 Python 做起来会比较简单,直接调用 itertools 模块中的 combinations 函数:

 1
 2from itertools import combinations
 3
 4
 5def main():
 6    while (nums := tuple(map(int, input().split(" "))))[0] != 0:
 7        for c in combinations(nums[1:], 6):
 8            print(*c)
 9
10
11if __name__ == "__main__":
12    main()

Problem 572 | 密码箱

  • Description

小明的密码箱打不开了,小明的密码箱是传统的3位滚轮密码。小明完全不记得他的密码了,所以他从 000开始以升序开始尝试,他已经试到第abc位密码了,可是箱子还是没有打开,他希望你将之后所有可能尝试的密码输出,这样他就可以完全不去思考,让他波动密码盘更有效率

  • Input

每行输入一个整数n(0 < n < 1000);n没有前缀0。

  • Output

n之后所有可能尝试的密码;输出有前缀0的。

  • Sample Input
1989
  • Sample Output
 1990
 2991
 3992
 4993
 5994
 6995
 7996
 8997
 9998
10999

好睿智的问题

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4
 5int n;
 6
 7int main() {
 8    while (scanf("%d", &n) != EOF) {
 9        while (n < 999) {
10            printf("%03d\n", ++n);
11        }
12    }
13    return 0;
14}

Problem 621 | 字符串统计

  • Description

对于给定的一个字符串,统计其中数字字符出现的次数。

  • Input

输入数据有多组,第一行是一个整数n,表示测试实例的个数,后面跟着n行,每行包括一个由字母和数字组成的字符串。

  • Output

对于每个测试实例,输出该串中数值的个数,每个输出占一行。

  • Sample Input
12
2asdfasdf123123asdfasdf
3asdf111111111asdfasdfasdf
  • Sample Output
16
29
 1#include <iostream>
 2
 3using namespace std;
 4
 5int n, cnt;
 6string s;
 7
 8int main() {
 9    cin >> n;
10    while (n--) {
11        cnt = 0;
12        cin >> s;
13        for (const auto& c : s) {
14            if (c >= '0' && c <= '9') {
15                cnt++;
16            }
17        }
18        cout << cnt << endl;
19    }
20    return 0;
21}

Problem 574 | 丑数

  • Description

只有质数2,3,5,7这几个作为因子的数叫做,丑数,比如前20个丑数是(从小到大来说) 1,2,3,4,5,6,7,8,9,10,12,14,15,16,18,20,21,24和25.

  • Input

我们给你个n(1<= n<=5842)当输入n为0结束。

  • Output

输出第n个丑数。每个数一行。

  • Sample Input
11
22
33
44
511
  • Sample Output
11
22
33
44
512

使用动态规划,un[i] 表示第 i 个丑数。

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4#include <vector>
 5#include <algorithm>
 6
 7
 8using namespace std;
 9
10int main() {
11    int n, i, ptr2, ptr3, ptr5, ptr7, nxt2, nxt3, nxt5, nxt7;
12    while (scanf("%d", &n) && n != 0) {
13        vector<int> un(n);
14        un[0] = 1;
15        nxt2 = 2;
16        nxt3 = 3;
17        nxt5 = 5;
18        nxt7 = 7;
19        ptr2 = 0;
20        ptr3 = 0;
21        ptr5 = 0;
22        ptr7 = 0;
23
24        for (i = 1; i < n; i++) {
25            un[i] = min({nxt2, nxt3, nxt5, nxt7});
26
27            if (un[i] == nxt2) {
28                ptr2++;
29                nxt2 = 2 * un[ptr2];
30            }
31            if (un[i] == nxt3) {
32                ptr3++;
33                nxt3 = 3 * un[ptr3];
34            }
35            if (un[i] == nxt5) {
36                ptr5++;
37                nxt5 = 5 * un[ptr5];
38            }
39            if (un[i] == nxt7) {
40                ptr7++;
41                nxt7 = 7 * un[ptr7];
42            }
43        }
44
45        printf("%d\n", un[n - 1]);
46    }
47    return 0;
48}

Problem 575 | 矩形

  • Description

在测试超大规模集成电路时,对给定的一个设计,专家要检测元件是否相互遮盖。一个元件可视为一个矩形,假设每个矩形都是水平排列的(边与x轴或y轴平行),所以长方形由最小的和最大的x,y坐标表示。
编程计算完全被覆盖的矩形个数。

  • Input

输入有多组长方形实例。对每组长方形,第一个数字是长方形的数量,然后是长方形的最小和最大x,y坐标(最小x,最大x,最小y,最大y)。

  • Output

对每组输入数据,输出一行,是被完全覆盖的长方形数量。

  • Sample Input
13
2100 101 100 101
30 3 0 101
420 40 10 400
54
610 20 10 20
710 20 10 20
810 20 10 20
910 20 10 20
  • Sample Output
10
24
 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4#include <vector>
 5
 6using namespace std;
 7
 8struct Rect {
 9    int xmin, ymin, xmax, ymax;
10};
11
12int main() {
13    vector<Rect> chips;
14    int n, cnt;
15
16    while (scanf("%d", &n) != EOF) {
17        cnt = 0;
18        chips.clear();
19        while (n--) {
20            Rect r;
21            scanf("%d %d %d %d", &r.xmin, &r.xmax, &r.ymin, &r.ymax);
22            chips.push_back(r);
23        }
24
25        for (int i = 0; i < chips.size(); i++) {
26            for (int j = 0; j < chips.size(); j++) {
27                if (i != j) {
28                    if (chips[j].xmin <= chips[i].xmin &&
29                        chips[j].xmax >= chips[i].xmax &&
30                        chips[j].ymin <= chips[i].ymin &&
31                        chips[j].ymax >= chips[i].ymax) {
32                        cnt++;
33                        break;
34                    }
35                }
36            }
37        }
38
39        printf("%d\n", cnt);
40    }
41
42    return 0;
43}

最开始我的思路是,如果矩形 A 能够被矩形 B 完全覆盖,那么 A 的面积必然小于等于 B 的面积,因此先将所有矩形按照面积从小到大排序,然后使用两层循环,不重不漏两两比较。但是这样做的问题是,两个面积和形状相等的矩形无法排序,并且按照题意他们互相“完全覆盖”,所以应该算两次。

Problem 1639 | 抽奖

  • Description

公司举办年会,为了活跃气氛,设置了摇奖环节。参加聚会的每位员工都有一张带有号码的抽奖券。现在,主持人依次公布 n 个不同的获奖号码,小谢看着自己抽奖券上的号码 num,无比紧张。请编写一个程序,如果小谢获奖了,请输出他中的是第几个号码;如果没有中奖,请输出 0。

  • Input

第一行一个正整数 n,表示有 n 个获奖号码,$2<n≤100$。
第二行包含 n 个正整数,之间用一个空格隔开,表示依次公布的 n 个获奖号码。
第三行一个正整数 num,表示小谢抽奖券上的号码。
1≤获奖号码,num<10000。

  • Output

一行一个整数,如果小谢中奖了,表示中奖的是第几个号码;如果没有中奖,则为 0。

  • Sample Input
17
217 2 3 4 9555 6 1
33
  • Sample Output
13
  • Hint

暴力
单组输入

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4
 5int n, i, num, nums[10005] = { 0 };
 6
 7int main() {
 8    scanf("%d", &n);
 9    for (int i = 1; i <= n; i++) {
10        scanf("%d", &num);
11        nums[num] = i;
12    }
13    scanf("%d", &num);
14    printf("%d\n", nums[num]);
15    return 0;
16}

Problem 1640 | 比身高

  • Description

有 N 个人排成一排,假设他们的身高均为正整数,请找出其中符合以下条件的人:排在他前面且比他高的人数与排在他后面且比他高的人数相等。

  • Input

第一行为一个正整数 N,$1<N<1000$,表示有多少个人。
下面 N 行,每行一个正整数,表示从前往后每个人的身高,假设每个人的身高≤10000。

  • Output

一行一个整数,表示满足这个条件的人数。

  • Sample Input
14
21
32
41
53
  • Sample Output
12
  • Hint

第 3、第 4 个人满足条件。 单组输入

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4
 5int main() {
 6    int n;
 7    int p[1001] = { 0 };
 8    int front = 0, rear = 0, cnt = 0;
 9    int i, j;
10    scanf("%d", &n);
11
12    for (i = 0; i < n; i++) {
13        scanf("%d", &p[i]);
14    }
15    for (i = 0; i < n; i++) {
16        front = 0;
17        rear = 0;
18        for (j = 0; j < i; j++) {
19            if (p[j] > p[i]) {
20                front++;
21            }
22        }
23        for (j = i + 1; j < n; j++) {
24            if (p[j] > p[i]) {
25                rear++;
26            }
27            if (rear > front) {
28                break;
29            }
30        }
31        if (rear == front) {
32            cnt++;
33        }
34    }
35    printf("%d", cnt);
36    return 0;
37}

Problm 1642 | 楼层编号

  • Description

小林在 NOIP 比赛期间住在“新世界”酒店。和其他酒店不一样的是,这个酒店每天都有一个高能的数字 t,这个数字在楼层中是不会出现的,以 t=3 为例,则 3、13、31、33 等楼层是不存在的,楼层编号为 1,2,4,5,…所以实际上的 4 楼才是 3 楼。
已知小林预订了编号为 m 层的房间,并且当天高能数字是 t,现在他想知道房间所在的真实楼层是多少。

  • Input

一行两个整数 m 和 t,1≤m≤100000,0≤t≤9,保证 m 对 t 合法。

  • Output

一行一个整数,表示真实楼层。

  • Sample Input
114 3
  • Sample Output
112
  • Hint

单组输入

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4#include <string>
 5
 6using namespace std;
 7
 8int main()
 9{
10    int m;
11    char t;
12    scanf("%d %c", &m, &t);
13    int leap = 0;
14    for (int i = 1; i <= m; i++) {
15        if (to_string(i).find(t) != string::npos) {
16            leap++;
17        }
18    }
19    printf("%d\n", m - leap);
20    return 0;
21}

Problem 1643 | 比例简化

  • Description

在社交媒体上,经常会看到针对某一个观点同意与否的民意调查以及结果。例如,对某观点表示支持的有 1498 人,反对的有 902 人,那么其比例可以简单地记为1498∶902。
因该比例的数值太大,难以一眼看出它们的关系。若把比例记为 5∶3,虽然与真实结果有一定的误差,但依然能够较为准确地反映调查结果,同时也显得比较直观。
现给出支持人数 A 和反对人数 B,以及一个上限 L,请将 A 比 B 化简为 A′ 比 B′,要求在 A′和 B′ 均不大于 L,且 A′ 和 B′ 互质(两个整数的最大公约数为 1)的前提下,A′/B′≥ A/B 且 A′/B′-A/B 的值尽可能小。

  • Input

一行三个整数 A,B,L,每两个整数之间用一个空格隔开,分别表示支持人数、反对人数以及上限。

  • Output

一行两个整数 A′ 和 B′,中间用一个空格隔开,表示化简后的比例。

  • Sample Input
11498 902 10
  • Sample Output
15 3
  • Hint

单组输入,$1\leq A,B;eq1000000,1\leq L\leq100,\frac{A}{B}\leq L$

由于 $L\leq100$,可以尝试遍历 A' 和 B'。

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4
 5using namespace std;
 6
 7int gcd(int a, int b) {
 8    return b == 0 ? a : gcd(b, a % b);
 9}
10
11int main()
12{
13    double a, b, ratio, e, emin = 1000001.0;
14    int l, p, q;
15    scanf("%lf%lf%d", &a, &b, &l);
16    ratio = a / b;
17    for (int i = 1; i <= l; i++) {
18        for (int j = 1; j <= l; j++) {
19            e = (i * 1.0) / (j * 1.0) - ratio;
20            if (e >= 0 && e < emin && gcd(i, j) == 1) {
21                emin = e;
22                p = i;
23                q = j;
24            }
25        }
26    }
27    printf("%d %d\n", p, q);
28    return 0;
29}

这里要注意误差 e 是可以为 0 的。

让 A' B' 都遍历 1 到 L 之间的所有数,会在一些绝对没有用的数据上浪费时间,比如 $A=1498,B=902,L=10$ 时绝对不会得到 $A'=1,B'=10$ 的结果。根据要求 $\frac{A}{B}\geq\frac{A'}{B'}$ 得到 $A'\leq\frac{A}{B}\cdot B'$。因此只需要遍历 B',随后根据原比例求出 A',再比较出最合适的比例。这样就将时间复杂度从 O(n^2) 降低到 O(n)

 1#define _CRT_SECURE_NO_WARNINGS
 2
 3#include <cstdio>
 4#include <cmath>
 5
 6using namespace std;
 7
 8int gcd(int a, int b) {
 9    return b == 0 ? a : gcd(b, a % b);
10}
11
12int main() {
13    int A, B, L;
14    int i, j, p, q;
15    double e, emin = 1000001.0;
16    scanf("%d%d%d", &A, &B, &L);
17    double ratio = (A * 1.0) / B;
18    for (j = 1; j <= L; j++) {
19        i = ceil(j * ratio);
20        if (i > L) {
21            break;
22        }
23        e = (i * 1.0) / j - ratio;
24        if (e < emin && gcd(i, j) == 1) {
25            emin = e;
26            p = i;
27            q = j;
28        }
29    }
30    printf("%d %d", p, q);
31    return 0;
32}

今天是周二也刚好是除夕,收工!

相关系列文章