2025寒假算法练习——Week 1

文章目录

习题均来自 NEFU OJ

Problem 357 | 天下无双

  • Description

太极生两仪,两仪生四象,四象生八卦,八卦生万物。天地初开,万物皆欲成双成对;芸芸丛生,谁愿孤苦伶仃。知音难觅,阳春白雪绕梁何久,千古一绝,神雕侠女驰骋九洲。
天下岂无双?千古绝唱为知音。情寄雨丝丝,述相思之意;梦随风万里,寻同道之人。共聚一堂,为梦想而努力;携手共进,为程序而疯狂!
夜夜编程不漫长,只因与君共拼搏……
给定n个数,其数值范围在1到n-1中,已知其中必有两个数是相同的,要求你找出并输出。(2<=n<=1,000,000)

  • Input

多组数据输入. 每组输入第一行一个数n。第二行n个数,其数值范围为1..n-1。

  • Output

每组输出一行一个数,即出现过两次的数。

  • Sample Input
15
22 3 1 4 2
38
47 6 1 2 3 5 4 7
  • Sample Output
12
27

第一眼看到这道题的时候,我想到了力扣两数之和,因而选择使用哈希表来解决这一问题:

 1import java.util.Scanner;
 2import java.util.Map;
 3import java.util.HashMap;
 4
 5public class Main {
 6    public static void main(String[] args) {
 7        try (Scanner scanner = new Scanner(System.in)) {
 8            while (scanner.hasNextInt()) {
 9                Map<Integer, Integer> map = new HashMap<>();
10                int n = scanner.nextInt();
11                for (int i = 0; i < n; i++) {
12                    int num = scanner.nextInt();
13                    int count = map.getOrDefault(num, 0);
14                    if (count == 1) {
15                        System.out.println(num);
16                    }
17                    map.put(num, count + 1);
18                }
19            }
20        }
21    }
22}

不过超时了……
随后我想到,使用 Map<Integer, Integer> 不如使用 int[] ,故修改代码如下:

 1import java.util.Scanner;
 2
 3public class Main {
 4    public static void main(String[] args) {
 5        try (Scanner scanner = new Scanner(System.in)) {
 6            while (scanner.hasNextInt()) {
 7                int n = scanner.nextInt();
 8                int[] nums = new int[n];
 9                for (int i = 0; i < n; i++) {
10                    int value = scanner.nextInt();
11                    if (nums[value - 1] == 1) {
12                        System.out.println(value);
13                    }
14                    nums[value - 1]++;
15                }
16            }
17        }
18    }
19}

还是 TLE……
这次我怀疑是语言的问题,遂改 C++ 尝试:

 1#include <iostream>
 2#include <vector>
 3
 4int main() {
 5
 6    int n;
 7    while (std::cin >> n) {
 8        std::vector<int> nums(n, 0);
 9
10        for (int i = 0; i < n; i++) {
11            int value;
12            std::cin >> value;
13            if (nums[value - 1] == 1) {
14                std::cout << value << std::endl;
15            }
16            nums[value - 1]++;
17        }
18    }
19
20    return 0;
21}

AC!

Problem 1604 | QWQ和彩色石-map

  • Description

QWQ来到了一个神奇的地方,这个地方有很多颜色不同的彩色石,每个颜色都可以用一个数字来代替,QWQ收集了一堆石子,他想知道这堆石子中颜色相同的石子个数的最大值

  • Input

第1行输入一个数字n,代表这堆石子的个数
第二行输入n个数,代表n个石子的颜色
保证输入的所有数都不超过100

  • Output

输出一个数字,代表颜色相同的石子个数的最大值

  • Sample Input
110
21 3 5 7 9 1 2 3 5 5
  • Sample Output
13

又是一个桶排序。

 1#include <iostream>
 2
 3int main() {
 4
 5    unsigned char stones[101] = {0}; // 既然数字都不超过 100,不妨用 unsigned char
 6    int n, k;
 7    std::cin >> n;
 8    for (int i = 0; i < n; i++) {
 9        std::cin >> k;
10        stones[k]++;
11    }
12
13    int max = 0;
14    for (int i = 0; i < 101; i++) {
15        if (stones[i] > max) {
16            max = stones[i];
17        }
18    }
19
20    std::cout << max << std::endl;
21
22    return 0;
23}

AC!

Problem 1605 | QWQ和翻译机

  • Description

QWQ拥有一台破烂的翻译机,作为他在星际旅行时候的必备物品,某日,他来的一颗名为倒置星的星球,这个星球上的所有东西都是倒置的,就连说话也要倒过来说,比如,translate在这颗星球上就是etalsnart,QWQ想依靠这台破烂的翻译机完成语言交流,然而,这台翻译机每次翻译的结果并不一定是正确的,你能告诉QWQ每次翻译的结果是否正确么?如果争取就输出’YES‘,否则输出’NO‘。

  • Input

每组输入占2行
第一行位QWQ想说的话
第二行位翻译机翻译的结果
每句话的长度不超过100个字符

  • Output

每组输出占一行
YES或者NO

  • Sample Input
1样例一:
2code
3edoc
4
5样例二:
6abb
7aba
  • Sample Output
1样例一:
2YES
3
4样例二:
5NO

简单的字符串倒置,语法题。

 1import java.util.Scanner;
 2
 3public class Main {
 4    public static void main(String[] args) {
 5        Scanner scanner = new Scanner(System.in);
 6        String raw = scanner.nextLine();
 7        String translated = scanner.nextLine();
 8        scanner.close();
 9
10        StringBuilder sb = new StringBuilder(raw);
11        System.out.println(sb.reverse().toString().equals(translated) ? "YES" : "NO");
12    }
13}

AC!

Problem 1606 | QWQ和棋局挑战

  • Description

众所周知QWQ的棋艺已经到了独孤求败的地步,某日一位来自泰坦星的勇士前来向QWQ发起挑战,不过挑战的项目却非常奇怪:这位勇士要求QWQ在一个n x n大小的棋盘上放置k个棋子,并要求放置后的棋盘上每一行和每一列最多有一个棋子,显然这个问题是如此的简单,所以这位勇士要求QWQ告诉他这样的棋局共有多少种? 你能帮助QWQ解决这个问题么?

  • Input

每组输入占据一行
一行有两个数字 分别表示棋盘的大小n和要求放置的棋子k
0<n<9 , 0<k<=n

  • Output

每组输入占据一行
输出这样的棋局的种类数目

  • Sample Input
1样例一:
22 1
3
4样例二:
52 2
  • Sample Output
1样例一:
24
3
4样例二:
52

这是一道简单的排列组合问题。首先在n行中无序选出k行,再在n列中有序选出k列,即

 1import java.util.Scanner;
 2
 3public class Main {
 4    public static void main(String[] args) {
 5        Scanner scanner = new Scanner(System.in);
 6        int n = scanner.nextInt();
 7        int k = scanner.nextInt();
 8        scanner.close();
 9
10        System.out.println(combination(n, k) * arrangement(n, k));
11    }
12
13    public static int arrangement(int n, int k) {
14        if (k == 1) {
15            return n;
16        }
17        int result = 1;
18        for (int i = 1; i <= k; i++) {
19            result *= n - i + 1;
20        }
21        return result;
22    }
23
24    public static int combination(int n, int k) {
25        if (k > n / 2) {
26            k = n - k;
27        }
28        return arrangement(n, k) / arrangement(k, k);
29    }
30}

Problem 1607 | QWQ和神秘商人

  • Description

众所周知,QWQ热衷于星际探险和旅行,一天他来到了K星,在这里他遇到了一位神秘的商人,这位商人手中有n个宝物,每个宝物都有一定的价格和珍藏值。如果QWQ想从商人手中购买宝物,就只能花费宇宙中唯一的通货——永恒宝石,但是在K星上关于购买宝物有奇怪的规定:

购买者手中永恒宝石的数量必须大于或者等于想要购买宝物的价格;
每当完成一个交易,购买者手中永恒宝石的数量就会变成所购买的宝物的价格,不论购买者原来有多少个永恒宝石;

现在,QWQ手中有k个永恒宝石,如果他想换取最大的珍藏值,这个值是多少呢?

  • Input

每组输入有三行
第一行 宝物数量n,初始宝石数量k
第二行 每一个宝物的价格
第三行 每个宝物的珍藏值

1 <= n <= 100000
其余输入均不大于100

  • Output

输出最大的珍藏值

  • Sample Input
15 6
22 4 6 8 10
31 50 10 2 50
  • Sample Output
161

乍一看像是一个背包问题,但是拼尽全力无法写出状态转移方程,随后灵光乍现,这道题还是要用桶排序来解。

 1import java.util.Scanner;
 2
 3public class Main {
 4    public static void main(String[] args) {
 5        int n, k;
 6        int[] price = new int[100001];
 7        int[] value = new int[101];
 8
 9        Scanner scanner = new Scanner(System.in);
10        n = scanner.nextInt();
11        k = scanner.nextInt();
12        for (int i = 0; i < n; i++) {
13            price[i] = scanner.nextInt();
14        }
15        for (int i = 0; i < n; i++) {
16            value[price[i]] += scanner.nextInt();
17        }
18        scanner.close();
19
20        int maxValue = 0;
21        for (int i = 1; i <= k; i++) {
22            maxValue += value[i];
23        }
24        System.out.println(maxValue);
25    }
26}

AC!

Problem 1608 | QWQ和神奇的传送器

  • Description

《无敌破坏王2》上映啦!QWQ作为Disney的忠实粉丝当然去贡献票房了。电影里的无敌破坏王和云妮洛普来到了互联网的世界,在这里,每个上网的人都是一个个虚拟的相同的小人物,当他们点击到某个网站时,虚拟的人物会乘坐一个神奇的传送器前往目的网站。
QWQ看到这个细节认为一个神奇的传送器只搭载一个人传输效率太低了,所以他觉得如果每个传送器能够搭载无数个人就好了,那么如果此时只有m个神奇的传送器,但有n个人等着被传送,不允许传送器有空载并且每个人看作是相同的,有多少种安排方式呢?

  • Input

先输入一个t,代表数据的组数
每组数据只有一行有两个数字,之间用一个空格作为间隔,分表代表m、n
保证1<=m<=n<=12

  • Output

对于每组数据,输出一个数字,代表安排方式的种数

  • Sample Input
11
21 2
  • Sample Output
11

题目描述讲的不明不白,给的测试用例也莫名其妙。做出来后知道这个排列组合问题的设定是,m 个传送器互不相同,n 个人相同。

尝试使用动态规划解决问题。用 dp[i][j] 表示有 j 个人分配给 i 个传送器,想要处理这种情况,有两种可能的前置情况:

  • 前 j-1 个人已经分配给了 i 个传送器,此时新添一个人坐到任意一个传送器上;
  • 前 j-1 个人分配给 i-1 个传送器,新来的一个人单独坐一个传送器。

因此得到状态转移方程 dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1]

另一个问题是 dp 初始化问题。有以下几部分需要考虑:

  • 当只有一个传送器时,即 i=1 时,所有人只能坐一个传送器,因此无论 j 为多少,dp[i][j] 均为 1;
  • 当人数和传送器数量相等时,即 i=j 时,一人一个传送器,dp[i][j] 均为 1;
  • 当传送器数量大于人数时,即 j>i 时,有些传送器空载,状态无效。

根据得到的信息写程序:

 1import java.util.Scanner;
 2
 3public class Main {
 4
 5    public static void main(String[] args) {
 6        Scanner scanner = new Scanner(System.in);
 7        int t = scanner.nextInt();
 8
 9        while (t-- > 0) {
10            int m = scanner.nextInt();
11            int n = scanner.nextInt();
12            System.out.println(countWays(m, n));
13        }
14
15        scanner.close();
16    }
17
18    private static int countWays(int m, int n) {
19        int[][] dp = new int[m + 1][n + 1];
20
21        for (int i = 1; i <= m; i++) {
22            dp[i][i] = 1;
23        }
24        for (int i = 1; i <= n; i++) {
25            dp[1][i] = 1;
26        }
27
28        for (int i = 2; i <= m; i++) {
29            for (int j = i + 1; j <= n; j++) {
30                dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1];
31            }
32        }
33
34        return dp[m][n];
35    }
36}

AC!

等等!这里的 dp 数组似乎是一个杨辉三角,而杨辉三角又可以表示为二项式系数,二项式系数又可以表示为组合数,难道说……

用隔板法也可以解决这道题!

 1import java.util.Scanner;
 2
 3public class Main {
 4
 5    public static void main(String[] args) {
 6        Scanner scanner = new Scanner(System.in);
 7        int t = scanner.nextInt();
 8
 9        while (t-- > 0) {
10            int m = scanner.nextInt();
11            int n = scanner.nextInt();
12            System.out.println(combination(m - 1, n - 1));
13        }
14
15        scanner.close();
16    }
17
18    private static int combination(int m, int n) {
19        if (m > n / 2) {
20            m = n - m;
21        }
22        return arrangement(m, n) / arrangement(m, m);
23    }
24
25    private static int arrangement(int m, int n) {
26        if (m == 1) {
27            return n;
28        }
29        int res = 1;
30        for (int i = n; i > n - m; i--) {
31            res *= i;
32        }
33        return res;
34    }
35}

还是 AC!

而且这里的 combination 函数还能继续优化,从而把 arrangement 函数抛弃掉:

 1import java.util.Scanner;
 2
 3public class Main {
 4
 5    public static void main(String[] args) {
 6        Scanner scanner = new Scanner(System.in);
 7        int t = scanner.nextInt();
 8
 9        while (t-- > 0) {
10            int m = scanner.nextInt();
11            int n = scanner.nextInt();
12            System.out.println(combination(m - 1, n - 1));
13        }
14
15        scanner.close();
16    }
17
18    private static int combination(int m, int n) {
19        if (m == 1) {
20            return n;
21        }
22        if (m == n) {
23            return 1;
24        }
25        if (m > n / 2) {
26            m = n - m;
27        }
28        int res = 1;
29        for (int i = 1; i <= m; i++) {
30            res = res * (n - m + i) / i;
31        }
32        return res;
33    }
34}

AC!

Problem 1609 | QWQ和QAQ

  • Description

QWQ的朋友QAQ开了一个A工厂,但QAQ不是一个很精明的老板,A工厂只生产三种产品,需要三种原材料,第一种产品分别消耗第一种原材料a1、第二种原材料b1、第三种原材料c1,第二种产品分别是a2、b2、c2,第三种产品分别是a3、b3、c3,但原材料总量是有限制的,分别是a、b、c,第一种产品可以盈利d1元,第二种产品可以盈利d2元,第三种原材料可以盈利d3元,由于每个产品都不可以分解,所以所有产品的生产量一定是整数。QAQ不知道怎么合理安排生产让他的盈利最大,于是他求助QWQ,QWQ更不知道了,但你一定知道

  • Input

先输入一个数字t(t<20),代表数组的组数
每组数据包括五行
第一行三个数字a1、b1、c1
第二行三个数字a2、b2、c2
第三行三个数字a3、b3、c3
第四行三个数字a、b、c
第五行三个数字d1、d2、d3
保证所有输入都是非负整数,并且不大于200

  • Output

输出最大的总盈利

  • Sample Input
11
21 1 1
31 1 1
41 1 1
53 3 3
61 2 3
  • Sample Output
19

一道三维完全背包问题:有三件物品,每件物品有三个维度的重量,而且每件物品都可以无限获取。
物品数量较低,考虑使用深搜:

 1import java.util.Scanner;
 2
 3public class Main {
 4    public static void main(String[] args) {
 5        Scanner sc = new Scanner(System.in);
 6        int t = sc.nextInt();
 7        while (--t >= 0) {
 8            int[] a = new int[4];
 9            int[] b = new int[4];
10            int[] c = new int[4];
11            int[] d = new int[4];
12
13            for (int i = 1; i <= 4; i++) {
14                a[i % 4] = sc.nextInt();
15                b[i % 4] = sc.nextInt();
16                c[i % 4] = sc.nextInt();
17            }
18            d[1] = sc.nextInt();
19            d[2] = sc.nextInt();
20            d[3] = sc.nextInt();
21
22            int s1 = 0, s2 = 0, s3 = 0;
23            System.out.println(dfs(a, b, c, d, s1, s2, s3));
24        }
25        sc.close();
26    }
27
28    public static int dfs(int[] a, int[] b, int[] c, int[] d, int s1, int s2, int s3) {
29        int usedA = a[1] * s1 + a[2] * s2 + a[3] * s3;
30        int usedB = b[1] * s1 + b[2] * s2 + b[3] * s3;
31        int usedC = c[1] * s1 + c[2] * s2 + c[3] * s3;
32        if (usedA > a[0] || usedB > b[0] || usedC > c[0]) {
33            return 0;
34        }
35
36        int currentProfit = d[1] * s1 + d[2] * s2 + d[3] * s3;
37        int profit1 = dfs(a, b, c, d, s1 + 1, s2, s3);
38        int profit2 = dfs(a, b, c, d, s1, s2 + 1, s3);
39        int profit3 = dfs(a, b, c, d, s1, s2, s3 + 1);
40        int maxProfit = Math.max(Math.max(currentProfit, profit1), Math.max(profit2, profit3));
41        return maxProfit;
42    }
43}

超时了……再一看这道题,每一个数据都不超过 200,也就是说,即使在最极端的情况下,也就是每个维度的背包都为 200 而每件物品各个维度的重量都为 1 时,一件物品最多有 200 件,所以试试枚举。

 1import java.util.Scanner;
 2
 3public class Main {
 4    public static void main(String[] args) {
 5        Scanner sc = new Scanner(System.in);
 6        int t = sc.nextInt();
 7        while (--t >= 0) {
 8            int[] a = new int[4];
 9            int[] b = new int[4];
10            int[] c = new int[4];
11            int[] d = new int[4];
12
13            for (int i = 1; i <= 4; i++) {
14                a[i % 4] = sc.nextInt();
15                b[i % 4] = sc.nextInt();
16                c[i % 4] = sc.nextInt();
17            }
18            d[1] = sc.nextInt();
19            d[2] = sc.nextInt();
20            d[3] = sc.nextInt();
21
22            System.out.println(enumerate(a, b, c, d));
23        }
24        sc.close();
25    }
26
27    public static int enumerate(int[] a, int[] b, int[] c, int[] d) {
28        int maxProfit = 0;
29        for (int s1 = 0; s1 <= 200; s1++) {
30            for (int s2 = 0; s2 <= 200; s2++) {
31                for (int s3 = 0; s3 <= 200; s3++) {
32                    int usedA = a[1] * s1 + a[2] * s2 + a[3] * s3;
33                    int usedB = b[1] * s1 + b[2] * s2 + b[3] * s3;
34                    int usedC = c[1] * s1 + c[2] * s2 + c[3] * s3;
35                    if (usedA > a[0] || usedB > b[0] || usedC > c[0]) {
36                        break;
37                    }
38                    int currentProfit = d[1] * s1 + d[2] * s2 + d[3] * s3;
39                    maxProfit = Math.max(maxProfit, currentProfit);
40                }
41            }
42        }
43        return maxProfit;
44    }
45}

AC!

Problem 1610 | 海贼的奖品赞助

  • Description

本次ACM校赛得到了海贼科技的小部分奖品的赞助(30个水杯),尽管赞助的奖品不多,但也是要感谢赞助商的!由于水杯是加急定制的,所以生产水杯的的过程和以往不同,从而导致了水杯的高度竟然是不一样的;规定水杯高度低于20.00CM 为不合格的产品,你能计算一下这批水杯的合格率吗?

  • Input

本题单组数据!
输入数据第一行为n,表示水杯的数量;接下来是这n个水杯的高度(实数);

  • Output

输出这批水杯的合格率(保留2位小数);合格率=合格数量/总数

  • Sample Input
15
219.23 18.00 21.00 22.00 20.00
  • Sample Output
10.60

简单题

 1import java.util.Scanner;
 2
 3public class Main {
 4    public static void main(String[] args) {
 5        Scanner sc = new Scanner(System.in);
 6        double n = sc.nextDouble();
 7        int qualified = 0;
 8        for (int i = 0; i < n; i++) {
 9            if (sc.nextDouble() >= 20.00) {
10                qualified++;
11            }
12        }
13        sc.close();
14        System.out.println(String.format("%.2f", qualified / n));
15    }
16}

AC!

相关系列文章