2023 RoboCom 世界机器人开发者大赛-本科组(省赛) CAIP 完整版题解
文章目录
RC-u1 亚运奖牌榜
2022 年第 19 届亚运会即将在杭州召开,杭州已经做好准备欢迎全亚洲的观众一同参与亚运盛会了!
你正在开发一款跟亚运奖牌计算相关的 App。给定两个国家的获奖情况,你的任务是计算这两个国家/地区的奖牌情况,并确定哪个国家/地区要排在奖牌榜的前面。
输入格式
输入第一行是一个正整数 N (1≤N≤1000),表示总共有 N 条获奖记录。
接下来的每一行都是形如以下的一条记录:
C
i
,
P
i
C_i, P _i
Ci,Pi
其中
C
i
=
0
,
1
C_i =0,1
Ci=0,1,0 表示是第一个国家/地区,1 表示是第二个国家/地区;
P
i
=
1
,
2
,
3
P_i=1,2,3
Pi=1,2,3,1表示金牌,2 表示银牌,3 表示铜牌。
输出格式:
首先输出两行,第一行是第一个国家/地区的金牌、银牌、铜牌获得数,用空格隔开;第二行是第二个国家/地区的奖牌获奖情况,要求与格式同第一个国家/地区。
最后一行,如果是第一个国家/地区排在前面,输出 The first win!
,否则输出 The second win!
。
排在前面的定义是:先比较金牌数,金牌数较大的排在前面;如金牌数相等,比较银牌数,银牌数较大的在前面;如金牌银牌数都相等,则比较铜牌数,铜牌数较大的在前面。
保证数据不存在奖牌数完全相同的情况。
输入样例:
15
0 1
0 2
0 3
0 1
0 1
0 2
0 3
1 3
1 3
1 3
1 3
1 2
1 1
1 1
1 1
输出样例:
3 2 2
3 1 4
The first win!
题解 - 模拟
用vector存储每个人的奖牌情况, 然后排序。
时间复杂度: O ( 1 ) O(1) O(1)
参考代码
//
// Created by trudbot on 2023/7/15.
//
#include <bits/stdc++.h>
using namespace std;
int main () {
int n; cin >> n;
vector<vector<int>> ans(2, vector<int>(4, 0));
while (n --) {
int c, p; cin >> c >> p;
ans[c][p] ++;
}
for (auto &v : ans) {
for (int i = 1; i <= 3; i ++) {
cout << v[i];
if (i != 3) cout << " ";
}
cout << endl;
}
auto t = ans;
sort(ans.begin(), ans.end());
if (t == ans) cout << "The second win!";
else cout << "The first win!";
return 0;
}
RC-u2 出院
A:最近出了一个饮料营养等级你们知道吗?例如无糖的饮料是 A 级,可乐是 D 级……
B:那……无糖可乐是什么级别?
C:AD 级吧。
A:出院!
B:出什么院,你也给我进去!
以上是某群中一段有趣的对话。请你按照里面的逻辑,在已知某些饮料的等级的情况下,给饮料定级。定级的方法是:
- 如果是已知等级的饮料,直接输出等级;
- 对于一个新饮料的名字,你需要将名字拆成两个已知等级的部分,然后输出这个级别。例如:Diet是A,Coke是D,那么DietCoke就是AD;
- 如果新饮料无法拆解或者有多种拆解方法,统一定为 D 级。
输入格式:
输入第一行是两个正整数 N,M (1≤N,M≤100),表示已知的饮料有 N 种,需要定级的饮料有 M 种。
接下来首先是 N 行,每行是一个字符串和一个字符,表示一种饮料的名字和对应的等级,等级只有 A,B,C,D 四种。
然后是 M 行,每行是一个字符串,表示需要定级的饮料的名字。
所有饮料名字只包含有大小写字母,长度不超过 30,给定拥有等级的饮料的名字不会重复。
输出格式:
对于每一个需要定级的饮料,输出定好的定级。
输入样例:
5 6
Diet A
LowSugarTea B
Milk C
Coke D
Water A
DietCoke
Pepsi
Milk
CokeWater
GoodMilk
dietCoke
输出样例:
AD
D
C
DA
D
D
题解 - 模拟
用哈希表存储已知的每个字符串的等级。
对于需要判断的字符串s
:
- 首先判断是否已知, 若为真直接输出即可
- 枚举每个已知等级的字符串, 判断其是否为
s
的前缀,若为前缀则判断s
除去这部分前缀后的部分是否已知等级, 若都已知, 则拼接 - 此过程中记录拼接的次数, 若不为一则定为
D
级。
时间复杂度 O ( N M ) O(NM) O(NM)
参考代码
//
// Created by trudbot on 2023/7/15.
//
#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
int main () {
int n, m; cin >> n >> m;
map<string, string> level;
for (int i = 0; i < n; i ++) {
string s, lev; cin >> s >> lev;
level[s] = lev;
}
for (int i = 0; i < m; i ++) {
string s; cin >> s;
//如果已知等级
if (level.count(s)) cout << level[s] << endl;
else {
int cnt = 0;
string res;
for (auto &p : level) {
if (p.x.size() < s.size()
&& s.substr(0, p.x.size()) == p.x //是否为前缀
&& level.count(s.substr(p.x.size()))) //后缀是否已知
cnt ++, res = p.y + level[s.substr(p.x.size())];
}
if (cnt != 1) res = "D";
cout << res << endl;
}
}
return 0;
}
RC-u3 骰子游戏
在某个游戏中有一个骰子游戏。在游戏中,你需要投掷 5 个标准六面骰子(骰子为一个正方体,6 个面上分别有1、2、3、4、5、6中的一个数字,骰子的质量均匀),投出的点数根据组合会获得一个“获胜等级”。获胜等级从高到低如下:
- 五个同点数 - 五个骰子显示相同的点数
- 四个同点数 - 四个骰子显示相同的点数
- 葫芦 - 一对和一个三个同点数(如1、1、3、3、3)
- 六高顺子 - 投出的点数为 2、3、4、5、6
- 五高顺子 - 投出的点数为 1、2、3、4、5
- 三个同点数 - 三个骰子显示相同的点数(如1、1、1、2、3)
- 两对 - 投出的点数中有两对是相同的(如 1、1、2、2、3)
- 一对 - 投出的点数有一对是相同的(如 1、1、2、3、4)
- 无 - 除去以上的其他情况
给定你已经投出的一次结果,现在假设你可以选择任意个骰子重投一次,请问怎么样操作,才能最大化在重骰后获得更好的获胜等级的概率呢?
注意:更好的获胜等级需要严格地比当前的获胜等级更好,例如 1、1、2、2、3 如果重骰后变为 1、1、3、3、4 并不比当前的获胜等级更好。
输入格式:
输入第一行是一个正整数 T (1≤T≤10),表示接下来有多少组数据。
每组数据只有一行 5 个数字,表示第一次投出的 5 个骰子的点数。
输出格式:
对于每组数据输出三个整数,其中第一个整数为为了获得最大的概率需要重新骰几个骰子,后面的两个整数为重骰骰子后概率的最简分数,其中第二个整数为分子,第三个整数为分母。如果分子为 0,分母为 1。
如果有多种获得最大概率的情况,取重骰的骰子数最少的方案。
输入样例:
3
1 1 2 2 3
1 1 2 3 4
1 1 1 2 3
输出样例:
3 4 9
3 13 18
2 4 9
样例说明:
样例的第一组数据中,一种方案是:重骰最后三个骰子以获得最大的概率(只要重骰的有一个“1”或者三个均相等即可)。
RC-u4 相对论大师
在某个直播间里,观众常常会发送类似这样的弹幕:
鱼越大,鱼刺越大;鱼刺越大,肉越少;肉越少,鱼越小;所以鱼越大,鱼越小
这样通过一连串推导得出一个搞笑的结论的弹幕发送者被称为“相对论大师”。
现在给定一系列已有的推论,请你从给定的推论中挑选一些,组成一条类似于上面的弹幕,成为一名“相对论大师”。
输入格式:
输入第一行是一个正整数 N N N (1≤ N N N≤1000),表示总共有多少条推论。
接下来的 N N N 行,每行有两对四个元素,形如下:
A 0 B 1
每对元素表示一个论点:第一个是一个长度不大于 5 的、只包含大小写字母的字符串,称为论点的核心;第二个数字固定为 0 或者 1,代表论点核心的方向属性。为简单理解,你可以将 0 理解为正面方向,1 理解为负面方向。例如:
YuCi 0 Rou 1
就可以理解为鱼刺大,肉少
。
于是一行中的两个论点就形成一条推论,表示第一个核心某个方向的属性能推出第二个核心的某个方向的属性,即鱼刺越大,肉越少
。
输出格式:
按照弹幕格式输出一行,例如:
Yu 0 YuCi 0 YuCi 0 Rou 1 Rou 1 Yu 1 = Yu 0 Yu 1
具体格式要求为:在一行中输出从起始论点到最终论点的所有推论,论点格式与输入相同,论点间以1个空格分隔。随后输出等号(等号前后均有1个空格),最后是相互矛盾的起始和终止论点。
如果有多种方案,选择使用推论最少的;推论条数相同的输出任意一种方案均可。
在方案中每条推论仅可使用一次。保证有解,且给定的推论中没有相同的推论。
输入样例:
5
Yu 0 Yuci 0
Rou 1 Yu 1
Yuci 0 Rou 1
Yuci 0 Gutou 0
Gutou 0 Rou 0
输出样例:
Yu 0 Yuci 0 Yuci 0 Rou 1 Rou 1 Yu 1 = Yu 0 Yu 1
题解 - bfs
题意: 一个单向图, 每两个顶点为一组, 要求找到所有同一组内的顶点路径中的最短路径, 并输出。
本题的考点在于如何建图, 以及使用bfs找到并记录两个顶点的最短路径。
对于每个新论点, 我们给其2个未使用的相邻的顶点编号, 并且以较小的编号为基本编号。如yuci
的基本编号为1
, 那么yuci 0
的编号就为1
, yuci 1
的编号为2
。 同时我们记录每个编号的名称, 以及每个名称的基本编号,这样我们就完成了图的建立和相关信息的保存。
然后, 我们枚举每一个顶点组, 使用bfs获得最短路径, 在所有路径中取最短的有效路径为答案。
最后, 根据路径输出对应的信息即可。
时间复杂度: O ( N 2 ) O(N^2) O(N2)
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 4010;
int n, m;
string name[N];
vector<int> g[N];
unordered_map<string, int> id;
vector<int> bfs(int start, int end) {
queue<int> q;
vector<int> last(n + 1, 0);
q.push(start);
while (!q.empty()) {
int u = q.front(); q.pop();
if (u == end) break;
for (auto &v : g[u])
if (last[v] == 0)
last[v] = u, q.push(v);
}
vector<int> res;
do {
res.push_back(end), end = last[end];
} while (end != 0);
return res;
}
void print(int u, bool space) {
cout << name[u] << " " << u - id[name[u]];
if (space) cout << " ";
}
int main () {
cin >> m;
//编号、建图
for (int i = 1; i <= m; i ++) {
string s1, s2;
int st1, st2;
cin >> s1 >> st1 >> s2 >> st2;
if (!id.count(s1)) {
n += 2, id[s1] = n - 1, name[n - 1] = name[n] = s1;
}
if (!id.count(s2)) {
n += 2, id[s2] = n - 1, name[n - 1] = name[n] = s2;
}
g[id[s1] + st1].push_back(id[s2] + st2);
}
//取最短路径
vector<int> res(2000);
for (int i = 1; i <= n; i += 2) {
auto p1 = bfs(i, i + 1), p2 = bfs(i + 1, i);
if (res.size() > p1.size() && p1.size() > 1) res = p1;
if (res.size() > p2.size() && p2.size() > 1) res = p2;
}
//打印格式
for (int i = res.size() - 1; i >= 1; i --) {
print(res[i], true), print(res[i - 1], true);
}
cout << "= ", print(res.back(), true), print(res[0], false);
return 0;
}
RC-u5 相对成功与相对失败
注意:题面内容表达纯属娱乐,与现实无关!
网上常有人说:看 XX 只能度过一个相对成功/失败的人生。不妨假设把这个句式套用在“参加睿抗比赛“以及“玩手机游戏”上,那么有:
- “参加睿抗比赛”必然比“不参加睿抗比赛”要成功;
- “玩手机游戏“必然比“不玩手机游戏”要失败。
现在有 N 个人,已知这些人自己填写的是否参加了睿抗比赛以及是否玩手机游戏的情况,以及他们实际上的成功程度的排序顺序,请问最少有多少人在填写情况时说谎了?
输入格式:
输出第一行为一个正整数 T T T (1≤ T T T≤5),表示数据组数。
每组数据第一行是一个正整数 N N N (1≤ N N N≤ 1 0 5 10^5 105),表示总共的人数。
接下来的 N 行,第 i 行有两个数字 A i , B i A_i, B_i Ai,Bi,表示第 i i i 位参赛选手是否参加了睿抗比赛以及是否玩手机游戏,0 为没有参加/没有玩,1 为参加了/玩了。
最后一行有 N N N 个数,为一个选手编号 1 到 N N N 的排列,表示选手成功程度的排序。排序顺序从最成功到最失败。
选手编号从 1 开始。
输出格式:
对于每组数据,输出一个整数,表示最少的说谎人数。
输入样例:
3
5
1 0
1 0
0 0
0 0
0 1
1 2 3 4 5
5
1 0
1 0
0 0
0 0
0 1
5 4 3 2 1
5
1 0
0 1
0 0
0 1
1 1
4 2 1 3 5
输出样例:
0
3
2
样例说明:
对于样例中的第三组数据,一种可能是编号为 4 的选手和编号为 2 的选手说谎了。
题解-DP
根据
- “参加睿抗比赛”必然比“不参加睿抗比赛”要成功;
- “玩手机游戏“必然比“不玩手机游戏”要失败。
我们可以为每个人算出一个得分, 若“参加睿抗比赛”则得一分, “不玩手机游戏”则再得一分。
排序应该是根据分数不上升的排列的。
现给定了一个排列, 问最少有多少人撒谎。
换种方式理解, 也就是最多有多少人没有撒谎, 即最多有多少人是符合分数不上升排列的。
所以本问题即是经典问题: 最长不上升/下降子序列长度。
用朴素的最长不上升子序列做法时间复杂度是 O ( n 2 ) O(n^2) O(n2), 若要优化为 O ( n l o g n ) O(nlogn) O(nlogn)更是麻烦。
注意到本题中, 分数只可能取0, 1, 2
三种值。
所以可以换一种方式dp, 定义
d
p
[
i
]
dp[i]
dp[i]为当前以分数i
结尾的最长不上升子序列长度。
由于不上升, i
之前的分数必须不小于i
, 则
d
p
[
i
]
=
1
+
m
a
x
(
d
p
[
i
]
,
d
p
[
i
+
1
]
,
.
.
.
,
d
p
[
2
]
)
dp[i] = 1 + max(dp[i], dp[i + 1], ..., dp[2])
dp[i]=1+max(dp[i],dp[i+1],...,dp[2])
时间复杂度为 O ( n ) O(n) O(n)
参考代码
//
// Created by trudbot on 2023/7/15.
//
#include <bits/stdc++.h>
using namespace std;
int main () {
int T; cin >> T;
for (int t = 1; t <= T; t++) {
int n; cin >> n;
vector<int> s(n + 1, 0);
for (int i = 1; i <= n; i ++) {
int a, b; cin >> a >> b;
s[i] = a + 1 - b;
}
vector<int> dp(3, 0);
int mx = 0;
for (int i = 0; i < n; i ++) {
int x; cin >> x;
for (int j = s[x]; j < 3; j ++)
dp[s[x]] = max(dp[s[x]], dp[j] + 1);
mx = max(dp[s[x]], mx);
}
cout << n - mx << endl;
}
return 0;
}
吐槽
我又参加robocom, 又玩游戏, 那我究竟是更成功还是更失败呢🤡
都看到这了, 不妨点个赞吧
去我的博客获得更好的阅读体验
在pta教育超市中可以练习原题。
更多推荐
所有评论(0)