第二届 GUAT大学生程序设计大赛 第一场 题解(A-M)
前言
比赛的内容主要包括计算机科学的常用算法,基本的计算理论,(如:离散数学,具体数学,组合数学基础),数据结构基础,程序设计语言(规定是C/C++或者是Java、Python)。在本项比赛中考察学生的不仅仅是能够完成指定任务的程序,更要求在完成程序的功能的基础之上提高程序的运行效率与空间占用率。涵盖的考点范围:数学计算,排序,字符串处理,简单算法(搜索,枚举,查找,计数,前缀和等),简单数论,简单图论,简单动态规划,数据结构:数组,对象/结构,字符串,队列,栈,树,图等。
题解
A、Equational?
题意
给定一个分数\(\frac{a}{b}\),将其拆分为$$\frac{a}{b} = \frac{c_{1}}{d_{1}} + \frac{c_{2}}{d_{2}} + \frac{c_{3}}{d_{3}}$$
分子\(c_1 = c_2 = c_3\),分母\(d_1\) != \(d_2\) != \(d_3\),并且无论分子还是分母均为自然数。
题解
不妨假设下式已成立:$$\frac{a}{b} = \frac{c_{1}}{d_{1}} + \frac{c_{2}}{d_{2}} + \frac{c_{3}}{d_{3}}$$
令$$k_{i} × \frac{a}{b} = \frac{c_{i}}{d_{i}}$$
则$$\frac{a}{b} = \sum_{i=1}^3{( k_i×\frac{a}{b} )} = \frac{a}{b} × \sum_{i=1}^3{k_i} \iff \sum_{i=1}^3{k_i}=1$$
\(\because\)分子\(c_1 = c_2 = c_3\),分母\(d_1\) != \(d_2\) != \(d_3\)
且无论分子还是分母均为自然数
由鸽巢定理(抽屉原理)可知:分母必须大于等于\(6\)
\(\therefore\)只要分母是大于等于\(6\)的,就可以拆分为三个分子相同但分母不同的分数
即有通解:
只需要将上述通解的分子通分为相同数,即为此题的解。不妨将分母取6,得:
\[\frac{3}{6} + \frac{2}{6} + \frac{1}{6} = \frac{1}{2} + \frac{1}{3} + \frac{1}{6} = 1 \]故容易得到:
\[\frac{a}{b} = \frac{c_{1}}{d_{1}} + \frac{c_{2}}{d_{2}} + \frac{c_{3}}{d_{3}} = \frac{1}{2} × \frac{a}{b} + \frac{1}{3} × \frac{a}{b} + \frac{1}{6} × \frac{a}{b} \]时间复杂度:O(1) 空间复杂度:O(1)
代码实现
C++
#include<iostream>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int T;
int a, b;
/**
* ∵1 = 1/2 + 1/3 + 1/6
* 设 a/b 为 x,则:
* 1 * x = 1/2 * x + 1/3 * x + 1/6 * x
* ∴c1 = c2 = c3 = c = a; d1 = 2 * b, d2 = 3 * b, d3 = 6 * b
*/
void solve() {
cin >> a >> b;
cout << a << ' ' << 2 * b << ' ' << a << ' ' << 3 * b << ' ' << a << ' ' << 6 * b << '\n';
}
int main() {
IOS
cin >> T;
while (T --) solve();
return 0;
}
B、Characters games
题意
给定一个字符串,以\(a(2)b(2)\)的形式给出,括号内的数字,代表的就是这个字符连续出现的次数,并且括号内的数字不会超过10000。
例如:\(a(2)b(2)z(2)\)表示\(aabbzz\)
问:最后每个出现次数不为0的字符,出现的次数分别是多少?按字典序输出。
题解
双指针解法
当读取到一个字符时,即可将第一个指针指向该字符之后的两个位置,即数字开始的位置
然后另一个指针从第一个之后的第一个位置开始从前往后遍历,直至扫描到有括号停止
两个指针之间的数值,便是当前字符连续出现的次数
值得注意的是,一个字符是可以在这个字符串中出现多次的,因此统计的值需要累加
时间复杂度:O(n) 空间复杂度:O(1)
代码实现
C++
#include<iostream>
#include<cstring>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
string s;
long long mp[26];
int main() {
IOS
cin >> s;
for (int i = 0, j, k = s.size(); i < k; ++ i) {
if (islower(s[i])) {
long long num = 0;
for (j = i + 2; s[j] != ')'; ++ j) {
num = num * 10LL + s[j] - '0';
}
mp[s[i] - 'a'] += num;
i = j;
}
}
for (auto &it: mp) {
if (it) cout << it << ' ';
}
return 0;
}
Go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var s string
_, _ = fmt.Fscan(in, &s)
l := len(s)
cnt := [30]int{}
for i := 0; i < l; i++ {
ci := s[i]
i += 2
j := i
for s[i] != ')' {
i++
}
num, _ := strconv.Atoi(s[j:i])
cnt[ci-'a'] += num
}
for i := 0; i < 30; i++ {
if cnt[i] > 0 {
_, _ = fmt.Fprintf(out, "%d ", cnt[i])
}
}
_, _ = fmt.Fprintln(out)
}
Java
import java.util.Arrays;
import java.util.HashMap;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
String str = scan.next();
Integer nums[] = new Integer[26];
Arrays.fill(nums,0);
char s[] = str.toCharArray();
for(int i = 0; i < str.length(); i++){
if(s[i] == '('){
for(int j = i + 1; j < str.length(); j++){
if(s[j] == ')'){
String temp = str.substring(i + 1,j);
nums[(s[i - 1] - 'a')] += Integer.valueOf(temp);
i = j;
break;
}
}
}
}
for(int i = 0; i < 26; i++){
if(nums[i] != 0)
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
C、Orienteering
题意
类似于校园跑,要按顺序经过一些给定的点位。从一个点位到达另一个点位,优先选择一条最短路径。若有多条不同的路径都是最短距离,优先选择经过的点数最少的路径。
题解
Floyd算法求最短路,并且额外开一个数组,存储经过的点的数量
时间复杂度:O(\(n^3\)) 空间复杂度:O(\(n^2\))
代码实现
C++
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int N = 207;
int n, m, p, a, b, u, v, w;
int parr[N];
int mt[N][N];
int dis[N][N];
int edge[N][N];
void solve() {
memset(dis, 0x3f, sizeof(dis));
memset(edge, 0x3f, sizeof(edge));
scanf("%d%d%d", &n, &m, &p);
scanf("%d%d", &a, &b);
for (int i = 1; i <= p; i ++) {
scanf("%d", &parr[i]);
}
parr[0] = a; parr[++ p] = b;
for (int i = 1; i <= m; i ++) {
scanf("%d%d%d", &u, &v, &w);
mt[u][v] = mt[v][u] = w;
edge[u][v] = edge[v][u] = 1;
dis[u][v] = dis[v][u] = w;
}
for (int i = 1; i <= n; i ++) {
dis[i][i] = edge[i][i] = 0;
}
for (int k = 1; k <= n; k ++) { //floyd
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
if (dis[i][k] + dis[k][j] < dis[i][j]) {
dis[i][j] = dis[i][k] + dis[k][j];
edge[i][j] = edge[i][k] + edge[k][j];
} else if (dis[i][k] + dis[k][j] == dis[i][j]) {
edge[i][j] = min(edge[i][j], edge[i][k] + edge[k][j]);
}
}
}
}
int res1 = 0, res2 = 0;
for (int i = 1; i <= p; i ++) {
res1 += dis[parr[i - 1]][parr[i]];
res2 += edge[parr[i - 1]][parr[i]];
}
printf("%d %d\n", res1, res2);
}
int main() {
IOS;
int _t = 1;
while (_t--) solve();
return 0;
}
Go
package main
import (
"bufio"
"fmt"
"math"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, m, p int
_, _ = fmt.Fscan(in, &n, &m, &p)
var a, b int
_, _ = fmt.Fscan(in, &a, &b)
// 需要走的路径
pa := make([]int, p+2)
pa[0] = a
pa[p+1] = b
for i := 1; i <= p; i++ {
_, _ = fmt.Fscan(in, &pa[i])
}
// 图的路径
dis := make([][]int, n+1)
way := make([][]int, n+1)
for i := range dis {
dis[i] = make([]int, n+1)
}
for i := range way {
way[i] = make([]int, n+1)
}
var u, v, w int
for i := 0; i < m; i++ {
_, _ = fmt.Fscan(in, &u, &v, &w)
dis[u][v] = w
dis[v][u] = w
way[u][v] = 1
way[v][u] = 1
}
for i := 1; i <= n; i++ {
for j := 1; j <= n; j++ {
if dis[i][j] == 0 {
dis[i][j] = math.MaxInt16
}
}
dis[i][i] = 0
}
sumw := 0
sump := 0
for k := 1; k <= n; k++ {
for x := 1; x <= n; x++ {
for y := 1; y <= n; y++ {
if dis[x][k]+dis[k][y] < dis[x][y] {
dis[x][y] = dis[x][k] + dis[k][y]
way[x][y] = way[x][k] + way[k][y]
} else if dis[x][k]+dis[k][y] == dis[x][y] && way[x][k]+way[k][y] < way[x][y] {
way[x][y] = way[x][k] + way[k][y]
}
}
}
}
for i := 1; i <= p+1; i++ {
sumw += dis[pa[i-1]][pa[i]]
sump += way[pa[i-1]][pa[i]]
}
_, _ = fmt.Fprintf(out, "%d %d\n", sumw, sump)
}
D、Layers of progressive
题意
给点初始血量\(n\),需要通过\(m\)个关卡
在每个关卡会给个四个正整数\(a, b, x, y\)
其中,\(a, b\)的数值代表的四则运算如下:
1 | 2 | 3 | 4 |
---|---|---|---|
+ | - | * | / |
例如:\(a = 1\),\(x = 5\),那么代表的操作就是
\[n = n + x = n + 5 \]问:在游戏结束后,能剩余的最大血量是多少?
题解
不妨假设:每关的血量值越高,最后剩余的血量值就会越高
证明:
不妨令前一步的结果为\(n\),当前步的值为\(x\),对操作符做出以下分类讨论:
- \(+\) 操作:结果 = \(n + x\)
- \(-\) 操作:结果 = \(n - x\)
- \(*\) 操作:结果 = \(n * x\)
- \(/\) 操作:结果 = \(n / x\)
对于1:
不妨令\(n_1 \leq n_2\)恒成立,假设存在:
根据上式,可列出方程:
\[f(x,n_1,n_2)=n_1+x-n_2-x=n_1-n_2\leq0 \]\(\because\)\(n_1+x \leq n_2+x\)存在
\(\therefore\)前一步的\(n\)越大,会使得当前获取的结果越大
对于2:
不妨令\(n_1 \leq n_2\)恒成立,假设存在:
根据上式,列出方程:
\[f(x,n_1,n_2)=n_1-x-n_2+x=n_1-n_2\leq0 \]\(\because\)\(n_1-x \leq n_2-x\)存在
\(\therefore\)前一步的\(n\)越大,会使得当前获取的结果越大
对于3:
根据题意,列出方程:
方程左右两边对\(n\)求导,得:
\[f'(n)=x \]\(\because x∈N^+\)
那么方程
\(\therefore\)方程\(f(n)=x×n\)在定义域内单调递增
\(\therefore\)前一步的\(n\)越大,会使得当前获取的结果越大
对于4:
根据题意,列出方程:
方程左右两边对\(n\)求导,得:
\[f'(n)=\frac{1}{x} \]\(\because x∈N^+\)
那么方程
\(\therefore\)方程\(f(n)=\frac{n}{x}\)在定义域内单调递增
\(\therefore\)前一步的\(n\)越大,会使得当前获取的结果越大
由上述证明,可得结论:每一步最大化血量,最后剩余的血量值就会越高
时间复杂度:O(n) 空间复杂度:O(1)
代码实现
C++
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef long long ll;
using namespace std;
int m;
ll n;
ll calc(int p, int q) {
ll val = n;
switch(p) {
case 1:
val += q;
break;
case 2:
val -= q;
break;
case 3:
val *= q;
break;
case 4:
val /= q;
break;
}
return val;
}
void solve() {
int a, b, c, d;
cin >> a >> b >> c >> d;
n = max(calc(a, c), calc(b, d));
}
int main() {
IOS
cin >> n >> m;
while (m --) solve();
cout << n;
return 0;
}
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
Long n;
Integer m;
n = scan.nextLong();
m = scan.nextInt();
for(int i = 0; i < m; i++){
Integer a,b,c,d;
a = scan.nextInt();
b = scan.nextInt();
c = scan.nextInt();
d = scan.nextInt();
Long temp1 = n,temp2 = n;
switch (a){
case 1: temp1 += c; break;
case 2: temp1 -= c; break;
case 3: temp1 *= c; break;
case 4: temp1 /= c; break;
}
switch (b){
case 1: temp2 += d; break;
case 2: temp2 -= d; break;
case 3: temp2 *= d; break;
case 4: temp2 /= d; break;
}
if(temp1 > temp2){
n = temp1;
}else{
n = temp2;
}
//System.out.println(n);
}
System.out.println(n);
}
}
E、Step 20
题意
在二维网格图中,可以朝向上、下、左、右进行移动一格,但不能移动到有障碍物的格子。给点起始位置(\(a, b\)),询问q次能否在网格图中恰好移动\(20\)步抵达目的地(\(c_i, d_i\))?
题解
思路1:广度优先搜索
横纵坐标的范围均为[\(-10000, 10000\)]
那么若直接开一个\(20000×20000\)的二维图,显然时空太大
观察题目信息,可知:给点的起始坐标(\(a, b\))是固定不变的,且移动到目的坐标的步数仅为20步
那么不妨设置两个偏移量$$offset_x = 20 - a,\quad offset_y = 20 - b$$
通过给坐标设置偏移量以后,即可得到如下二维坐标图:
例如:起始坐标(\(a, b\))经过偏移得到(\(a + offset_x\), \(b + offset_y\)) = (\(20\), \(20\))
我们只需要维护偏移后横纵坐标均落在[\(0, 40\)]的点即可
由于加上了步数的限制,因此抵达一个坐标点,并不一定就是目标步数
所以不可以简单地用bool \(vis[x][y]\)标记是否抵达过某个坐标点(\(x, y\))
此时需要再引入一个新的维度,亦即需要以bool \(vis[x][y][z]\)来标记
bool \(vis[x][y][z]\)含义为:在第\(z\)步到达点(\(x, y\))
其他的操作和普通的广度优先搜索类似
时间复杂度:O(\(n + 40 × 40 × 20\)) 空间复杂度O(\(n + 40 × 40 × 20\))
思路2:动态规划
二维网格图优化思路类似于思路1,都是将坐标进行偏移映射
定义int \(dp[i][j][k]\)含义为:第\(i\)步是否能抵达坐标(\(j, k\)),若能,值为\(1\),若不能,值为\(0\)
状态转移方程:$$dp[i][j][k]\quad |=\quad dp[i - 1][j][k \pm 1] \quad | \quad dp[i - 1][j ± 1][k]$$
二维矩阵初始化:除了\(dp[0][a+offset_x][b+offset_y]\)置为1,其他均置为0
代码实现
C++(思路1)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define OBSTACLE 1
#define ROAD 0
using namespace std;
typedef tuple<int, int, int> TP;
constexpr static int DIRS[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
const int MX = 40, MI = 0;
int T, a, b, c, d, m, q;
int offset_x, offset_y;
int e[42][42];
bool vis[42][42][21];
//特殊测试用例
//(a,b)可达 但其他点均不可达 问(a,b)可不可达
void init() {
for (int i = 0; i < 42; ++ i) {
for (int j = 0; j < 42; ++ j) {
for (int k = 0; k <= 20; ++ k) {
vis[i][j][k] = false;
}
e[i][j] = ROAD;
}
}
}
inline bool check(int r, int c) {
return r <= MX && r >= MI && c <= MX && c >= MI;
}
void bfs() {
queue<TP> que;
que.push(TP(20, 20, 0));
vis[20][20][0] = true;
while (!que.empty()) {
TP tp = que.front();
que.pop();
int x = get<0>(tp), y = get<1>(tp), z = get<2>(tp);
for (int i = 0; i < 4; ++ i) {
int nex = x + DIRS[i][0], ney = y + DIRS[i][1];
if (check(nex, ney) && !vis[nex][ney][z + 1] && e[nex][ney] == ROAD) {
if (z < 19) que.push(TP(nex, ney, z + 1));
vis[nex][ney][z + 1] = true;
}
}
}
}
void solve() {
init();
cin >> a >> b >> m >> q;
offset_x = 20 - a, offset_y = 20 - b;//设置偏移量
for (int i = 0; i < m; ++ i) {
cin >> c >> d;
c += offset_x;
d += offset_y;
if (check(c, d)) {//是否在图内
e[c][d] = OBSTACLE;//障碍物
}
}
bfs();//广度优先遍历
while (q --) {
cin >> c >> d;
c += offset_x;
d += offset_y;
if (check(c, d) && vis[c][d][20]) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
}
int main() {
IOS
cin >> T;
while (T --) {
solve();
}
return 0;
}
C++(思路2)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int mp[43][43], dp[21][43][43];
int fstX, fstY;
int ret[2];
bool inRange(int y, int x) {
return (fstY - 20 <= y && y <= fstY + 20)
&& (fstX - 20 <= x && x <= fstX + 20);
}
void getIdx(int y, int x) {
ret[0] = y - fstY + 21;
ret[1] = x - fstX + 21;
}
void solve() {
memset(dp, 0, sizeof(dp));
memset(mp, 0, sizeof(mp));
int a, b, m, q, c, d, e, f;
scanf("%d%d%d%d", &a, &b, &m, &q);
fstX = a; fstY = b;
for (int i = 1; i <= m; ++ i) {
scanf("%d%d", &c, &d);
if (inRange(d, c)) {
getIdx(d, c);
mp[ret[0]][ret[1]] = 1;
}
}
getIdx(fstY, fstX);
dp[0][ret[0]][ret[1]] = 1;
for (int i = 1; i <= 20; ++ i) {
for (int j = 1; j <= 41; ++ j) {
for (int k = 1; k <= 41; ++ k) {
if (!mp[j][k]) {
dp[i][j][k] = dp[i - 1][j - 1][k] | dp[i - 1][j][k - 1] |
dp[i - 1][j + 1][k] | dp[i - 1][j][k + 1];
}
}
}
}
while (q --) {
scanf("%d%d", &e, &f);
if (inRange(f, e)) {
getIdx(f, e);
printf("%s\n", dp[20][ret[0]][ret[1]] ? "YES" : "NO");
} else {
printf("NO\n");
}
}
}
int main() {
IOS;
int _t; scanf("%d", &_t);
while (_t --) solve();
return 0;
}
F、Square toy
题意
给定一个共有\(n\)列的方块玩具,每一列初始的个数分别为:$$a_1,a_2,...,a_i,...,a_n$$
对方块玩具会改变四次重力,方向分别为:右、上、左、下。问:依次改变完全部的重力方向后,每一列的个数是多少?
题解
下述的行的数量和列的数量,指的均为一行/列的方块数量。
重力向右:对于行的数量不会发生变化,列的数量变为从右向左递减。
重力向左:对于行的数量不会发生变化,列的数量变为从左向右递减。
重力向上:对于列的数量不会发生变化,行的数量变为从上向下递减。
重力向下,对于列的数量不会发生变化,行的数量变为从下向上递减。
起始状态重力向下。所以易知排序即为最后的结果。
时间复杂度:O(nlogn) 空间复杂度:O(n)
代码实现
C++
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int n;
int a[1000007];
int main() {
IOS
cin >> n;
for (int i = 0; i < n; ++ i) cin >> a[i];
sort(a, a + n, [&](int &val1, int &val2) {
return val1 > val2;
});
for (int i = 0; i < n; ++ i) cout << a[i] << ' ';
return 0;
}
Go
package main
import (
"bufio"
"fmt"
"os"
"sort"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n int
_, _ = fmt.Fscanln(in, &n)
a := make([]int, n)
for i := 0; i < n; i++ {
_, _ = fmt.Fscan(in, &a[i])
}
sort.Slice(a, func(i, j int) bool { return a[i] > a[j] })
for i := 0; i < n; i++ {
_, _ = fmt.Fprintf(out, "%d ", a[i])
}
_, _ = fmt.Fprintln(out)
}
G、Types of words
题意
问一个四字成语的类型
题解
假设成语存于字符串\(s\)中。若\(s[i]\)是在字符串\(s\)中从左往右第一个出现的字符,则记录为类型\(A\),若是第二个出现的字符串则记录为类型\(B\),以此类推。比如字符串\(nine\),类型为\(ABAC\)。
代码实现上,只需要嵌套两层for循环,查看该字符在当前位置之前的位置是否已经出现过。若出现过,则记录为与之相同的类型;若未出现过,则记录为最新的类型。
时间复杂度:O(\(n^2\)) 空间复杂度:O(n)
代码实现
C++
#include<iostream>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int T;
char s[5], t[5];
void solve() {
cin >> s;
char ch = 'A';
for (int i = 1; i < 4; ++ i) {
bool flag = false;
for (int j = 0; j < i; ++ j) {
if (s[i] == s[j]) {
t[i] = t[j];
flag = true;
break;
}
}
if (!flag) t[i] = ++ ch;
}
cout << t << '\n';
}
int main() {
IOS
t[0] = 'A', t[4] = '\0';
cin >> T;
while (T --) solve();
return 0;
}
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
for (int i = 0; i < n; i++) {
String str = scan.next();
char s[] = str.toCharArray();
char ans[] = new char[4];
ans[0] = 'A';
char ch = 'A';
for(int j = 1; j < 4; j++){
Integer judge = 0;
for(int k = 0; k < j; k++){
if(s[k] == s[j]){
ans[j] = ans[k];
judge = 1;
break;
}
}
if(judge == 0){
ans[j] = ++ch;
}
}
System.out.println(ans);
}
}
}
H、Card game
题意
\(n\)个玩家共抽取\(m\)张卡牌,每张卡牌上有一个数值,并保证:\(m\) % \(n\) == \(0\) 恒成立。
对于\(n\)个玩家抽取完一轮卡牌后(即每个人各抽一张),就统计个人总得分。计分方案为:
问:全部卡牌被抽取完成后,积分最大者为胜者。输出全部胜者。
题解
维护每个玩家的总得分,总得分除以游戏轮次即为平均数
用map<int, int>维护每个数的出现次数,出现次数最多者为众数
时间复杂度:O(nlogn) 空间复杂度:O(n)
代码实现
C++
#include<bits/stdc++.h>
#define PII pair<int, int>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;
int n, m, w;
struct Node {
int id;//玩家编号
ll score;//分数
ll sum;//全部数值之和
map<int, int> mp;//存每个数出现的次数
PII p;//first为众数,second为数目
};
int main() {
IOS
cin >> n >> m;
vector<Node> node(n);
for (int i = 0; i < n; ++ i) {
cin >> node[i].id;
node[i].sum = node[i].score = node[i].p.first = node[i].p.second = 0;
}
for (int j = 0; j < m; ++ j) {
cin >> w;
int i = j % n;
node[i].sum += w;
node[i].mp[w] ++;
if (node[i].mp[w] > node[i].p.second) {
node[i].p.first = w;
node[i].p.second = node[i].mp[w];
} else if (node[i].mp[w] == node[i].p.second && w < node[i].p.first) {
node[i].p.first = w;
}
node[i].score += node[i].sum / (j / n + 1);//平均值
node[i].score += node[i].p.first;//众数
}
sort(node.begin(), node.end(), [&](Node &it1, Node &it2) {
if (it1.score != it2.score) return it1.score > it2.score;
return it1.id < it2.id;
});
cout << node[0].id << ' ';
for (int i = 1; i < n && node[i].score == node[i - 1].score; ++ i) {
cout << node[i].id << ' ';
}
return 0;
}
Java
import java.util.Arrays;
import java.util.HashMap;
import java.util.Scanner;
import java.util.stream.Collectors;
class People {
Integer No;
Long sum;
Long garde;
HashMap<Long, Integer> map;
}
public class Main {
// 获取众数
static Long GetMode(HashMap<Long, Integer> hashMap) {
Integer max = -1;
Long ans = 0L;
for (Long key : hashMap.keySet().stream().sorted().collect(Collectors.toList())) {
Integer value = hashMap.get(key);
if (value > max) {
max = value;
ans = key;
}
}
return ans;
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n, m;
n = scan.nextInt();
m = scan.nextInt();
People[] people = new People[n + 10];
Long max = -1L;
people[0] = new People();
for (int i = 1; i <= n; i++) {
People p = new People();
p.No = scan.nextInt();
p.sum = 0L;
p.garde = 0L;
p.map = new HashMap<>();
people[i] = p;
}
Long round = 0L;
// 模拟摸牌
for (int i = 1; i <= m; i++) {
Long temp = scan.nextLong();
int index = i % n;
if (index == 0) {
index = n;
}
people[index].sum += temp;
// map用于记录每个数出现的次数
if (!people[index].map.containsKey(temp))
people[index].map.put(temp, 1);
else {
people[index].map.put(temp, people[index].map.get(temp) + 1);
}
// 摸完一轮时,计算分数
if (index % n == 0) {
round++;
for (int j = 1; j <= n; j++) {
people[j].garde += people[j].sum / round;
people[j].garde += GetMode(people[j].map);
}
}
}
Arrays.sort(people, 1, n, (o1, o2) -> {
if (o1.garde.equals(o2.garde)) {
return o1.No - o2.No;
}
return o2.garde.compareTo(o1.garde);
});
max = people[1].garde;
for (int i = 1; i <= n; i++) {
if (people[i].garde.equals(max))
System.out.print(people[i].No + " ");
}
System.out.println();
}
}
I、3²+4²=5²
题意
输入一个正整数\(T\),代表询问\(T\)次,每次输入三个整数\(a, b, c\),问输入的测试用例是否能成为RT三角形的三条边?若能,输出“Yes”,否则输出“No”。
题解
判断下式是否成立即可:
时间复杂度:O(1) 空间复杂度:O(1)
代码实现
C++
#include<iostream>
#include<algorithm>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int T;
long long a[3];
inline bool isRt() {
sort(a, a + 3);
return a[0] * a[0] + a[1] * a[1] == a[2] * a[2];
}
void solve() {
for (int i = 0; i < 3; ++ i) cin >> a[i];
if (isRt()) {
cout << "Yes\n";
} else {
cout << "No\n";
}
}
int main() {
IOS
cin >> T;
while (T --) {
solve();
}
return 0;
}
Java
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static String Judge(Integer nums[]){
if(((nums[0] * nums[0]) + (nums[1] * nums[1])) == (nums[2] * nums[2]))
return "Yes";
return "No";
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
Integer n = scan.nextInt();
Integer nums[] = new Integer[3];
for(int i = 0; i < n; i++){
for(int j = 0; j < 3; j++){
nums[j] = scan.nextInt();
}
Arrays.sort(nums);
System.out.println(Judge(nums));
}
}
}
J、Palindromic number manipulators
题意
易枚举出正整数范围内的回文数:
会进行两次选取,范围分别为[\(l_{1}\), \(r_{1}\)]和[\(l_{2}\), \(r_{2}\)]
意为:截取从第\(l_{i}\)到第\(r_{i}\)个回文数,然后将截取到的回文数拼接为一个字符串,并保证该字符串长度不超过\(1000\)。
你需要将第一个字符串变为第二个字符串,你可以选择的操作有:
- 增加一个数字
- 删除一个数字
- 修改一个数字
你可以进行操作任意次,但每次只能选择其中一种操作。
问:将第一个字符串变为第二个字符串,最少需要花费的操作次数是多少?
题解
提示信息:第\(100000\)个回文数是\(900010009\)
根据提示信息,可知若从\(1\)暴力枚举到\(900010009\)必然超时。因此,需要直接枚举出前\(100000\)个回文数。
枚举思路:分为奇数个数位和偶数个数位进行从小到大枚举,详细枚举方法见参考代码
若不知道如何线性枚举回文数,也可以使用打表法。
将第一个字符串变为第二个字符串,只需要线性DP 编辑距离 即可。
时间复杂度:O(\(n^2\)) 空间复杂度:O(\(n^2\))
代码实现
C++
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef long long ll;
using namespace std;
const int N = 1007;
int m, n;
int dp[N][N];
int l[2], r[2];
string s[2];
vector<ll> hw;
void init(ll val) {
ll up = 1;//若会越界就开long long
char s[100];
sprintf(s, "%lld", val);
int len = strlen(s);
len = (len - 1) / 2;
while (len --) up *= 10;
for (ll u = 1; u <= up; u *= 10) {
//枚举奇数长度的回文数
for (ll i = u, j = u * 10; i < j; ++ i) {
ll x = i;
ll y = i;
x /= 10;//去掉最低位
while (x) {
y = y * 10 + x % 10;
x /= 10;
}
hw.emplace_back(y);
}
//枚举偶数长度的回文数
for (ll i = u, j = u * 10; i < j; ++ i) {
ll x = i;
ll y = i;
while (x) {
y = y * 10 + x % 10;
x /= 10;
}
hw.emplace_back(y);
}
}
}
void solve() {
for (int i = 0; i < 2; ++ i) {
for (int j = l[i] - 1; j < r[i]; ++ j) {
s[i] += to_string(hw[j]);
}
}
m = s[0].size(), n = s[1].size();
for (int i = 1; i <= m; ++ i) dp[i][0] = i;
for (int i = 1; i <= n; ++ i) dp[0][i] = i;
for (int i = 1; i <= m; ++ i) {
for (int j = 1; j <= n; ++ j) {
if (s[0][i - 1] == s[1][j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
}
}
}
}
int main() {
IOS
init(900010009);
cin >> l[0] >> r[0] >> l[1] >> r[1];
solve();
cout << dp[m][n];
return 0;
}
K、The score of the postgraduate re-examination
题意
共n个人,对于每个人:输入五个整数(0-100)和一个字符串(长度10以内)。
去掉一个最大值和一个最小值,剩余的数求平均数(下取整)。
问:平均分最高者,名字和分数分别是什么?若有多个相同,优先输出先输入的。
题解
思路1:排序
任意选择一种排序,排序后减去一个最大值和一个最小值即为总得分。由此,可得:
并且注意学生输入的顺序即可
时间复杂度:O(nlogn) 空间复杂度:O(n)
思路2:维护最值
一趟遍历维护出最大值
一趟遍历维护出最小值
一趟遍历维护处五个数总和
然后计算即可
时间复杂度:O(n) 空间复杂度:O(n)
代码实现
C++(思路1)
#include<iostream>
#include<functional>
#include<algorithm>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int T;
int a[5];
int ans_score;
string ans_name;
void solve() {
int n = 5, res = 0;
string name;
for (int i = 0; i < n; ++ i) cin >> a[i];
cin >> name;
make_heap(a, a + n, less<int>());//大根堆,第三个参数可以不写
do {
pop_heap(a, a + n);//堆顶元素出堆,放在堆末,参数与make_heap一致
res += a[0];//取堆顶元素
} while (-- n > 2);
if (ans_score < (res /= 3)) {
ans_score = res;
ans_name = name;
}
}
int main() {
IOS
cin >> T;
while (T --) {
solve();
}
cout << ans_score << ' ' << ans_name << '\n';
return 0;
}
Java(思路2)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
String name[] = new String[n];
Integer garde[][] = new Integer[n][5];
Integer ansMax = -1;
Integer ansMaxLoc = 0;
for(int i = 0; i < n; i++){
Integer min = 101;
Integer minLoc = 0;
Integer max = -1;
Integer maxLoc = 0;
for (int j = 0; j < 5; j++){
garde[i][j] = scan.nextInt();
if(garde[i][j] > max){
max = garde[i][j];
maxLoc = j;
}
if(garde[i][j] < min){
min = garde[i][j];
minLoc = j;
}
}
name[i] = scan.next();
Integer cnt = 0;
for(int j = 0; j < 5; j++){
if(j == maxLoc || j == minLoc)
continue;
cnt += garde[i][j];
}
if((cnt / 3) > ansMax){
ansMax = cnt / 3;
ansMaxLoc = i;
}
}
System.out.println(ansMax + " " + name[ansMaxLoc]);
}
}
L、Ph0t05h0p
题意
交互题,共有以下四种操作:
- add \(str\):增加一个名为str的图层
- modify \(x\) \(str\):修改第x个图层的名字为str,若不存在第x个图层,则操作无效
- list:打印出当前全部图层的名字,若不存在任何图层,输出“\(empty\)”
- undo:撤销上一步操作(仅add或modify),若不存在上一步操作,则操作无效
题解
用一个自定义栈\(stk\)(数组:允许随机存取)存储每一个图层的名字,设计目的:执行add操作时只需要加到栈顶,执行modify操作时也可以直接操作第x层,时间复杂的均为O(1)
由于撤销的操作仅包含add和modify两种,因此可以用一个bool来表示操作的类型
维护一个bool类型的栈\(un\),栈顶维护的是最新的可撤销操作类型
撤销一个modify操作,不妨反向思考:如何将修改后的值改回原来的值?
假设第\(x\)层图层(假设存在)原来的名字为\(a\),想要改为名字\(b\),现在执行了一次modify \(x\) \(b\)
想改回原来的值,只需要执行一次modify \(x\) \(a\)
因此,维护撤销modify操作,只需要用一个栈\(mo\)维护反向撤销操作即可
list操作直接打印\(stk\)即可
时间复杂度:O(n) 空间复杂度:O(n)
代码实现
C++
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int n, x;
string op, str;
vector<string> stk;
stack<bool> un;//true代表"add"操作,false代表"modify"操作
stack<pair<int, string>> mo;//存储反向修改操作
void add() {
cin >> str;
stk.push_back(str);
un.push(true);//记录add
}
void undo() {
if (!un.empty()) {
bool b = un.top();
un.pop();
if (b) {
stk.pop_back();
} else {
pair<int, string> p = mo.top();
mo.pop();
stk[p.first] = p.second;
}
}
}
void show() {
if (stk.empty()) {
cout << "empty" << endl;
} else {
for (auto &it: stk) {
cout << it << ' ';
}
cout << endl;
}
}
void modify() {
cin >> x >> str;
if (stk.size() <= x || x < 0) return ;//x不在合法范围内
mo.push(make_pair(x, stk[x]));//记录反向修改操作
stk[x] = str;//修改
un.push(false);//记录modify
}
int main() {
IOS
cin >> n;
while (n --) {
cin >> op;
if (op[0] == 'a') {
add();
} else if (op[0] == 'u') {
undo();
} else if (op[0] == 'l') {
show();
} else {
modify();
}
}
return 0;
}
M、Genshin Impact
题意
输出“I want to play the Genshin Impact!!!”
题解
整活题,hello world即可
时间复杂度:O(1) 空间复杂度:O(1)
代码实现
C++
#include <iostream>
using namespace std;
int main(){
cout << "I want to play the Genshin Impact!!!" << endl;
return 0;
}
Python 3
print("I want to play the Genshin Impact!!!")
总结
\(2024\)年 GUAT大学生程序设计大赛 第一场圆满结束!
本场比赛侧重数据结构与算法的基础知识的考察,拟定\(13\)道具有一定难度且具有一定创新性的竞赛题目。旨在以赛促学,增强学生基础编程能力,促进学生团结协作,营造校内良好的编程竞赛文化氛围。
从比赛结果看,大部分选手表现出色,展示了扎实的编程基础和较强的解决问题能力。然而,也有部分选手在某些题目上遇到困难。希望各位选手赛后好好复盘,查缺补漏,再接再厉!