2022 RoboCom 世界机器人开发者大赛-高职组(国赛)中的一道题
RC-v2 智能陪护
在VP的过程中,有一个case一直没过,就很苦恼。
然后网络寻找题解,2022 RoboCom 世界机器人开发者大赛-高职组(国赛)
里面是java版本,验证能AC,但查阅代码,感觉小小的脑袋充满了大大的问号。
我个人倾向于:
1,2二选一,3确定无疑,T_T
后文细细阐述
后话:
差不多累计VP了2022~2024年的所有睿抗编程赛,对个别题的题意描述,其实颇有微词,如果题目能加上对case的解读,甚至构造特定case来消除理解上歧义点,我觉得比赛体验感会更好。
智能陪护系统是一个自动为老人们分配机器人护工的系统。系统中有若干个机器人排队候选,当有老人下单时,系统自动将队列最前面的机器人派送给老人,该机器人就在老人下单的当天上岗了。当老人结算时,这个机器人就立刻结束任务,回到队列末尾排队等待下一次任务,必要时可以在当天就开始接任务。
本题就请你实现这样一个自动分配功能。
注意:如果同一天有多位老人下单或结算,保证先处理结算的,使得机器人能及时回到队列;然后处理下单——系统中另有一个下单队列,下单的老人们按照处理的顺序排队;最后分配,按下单队列中排队的顺序将机器人护工分配给老人们。
对于同时下达相同指令的,则按照老人ID的升序排队处理。如果系统中没有机器人护工了,则排队的老人们需要在下单队列中等待。
输入格式:
输入在第一行中给出 2 个正整数:N(≤ 10 4 10^4 104)是机器人护工的数量—— 这里假设所有机器人护工从 1 到 N 编号,且一开始按照编号升序在系统队列中排队;K(≤ 10 5 10^5 105)是系统订单信息的条数。随后 K 行,每行给出一条订单信息,格式为:
老人ID 指令 日期
其中 老人ID 是一个长度不超过 5 的字符串,由英文小写字母和数字组成;指令 为 1 表示下单,为 0 表示结算;日期 是按照 年年年年月月日日 格式给出的,保证是一个 2022 年 1 月 1 日到 3022 年 12 月 31 日 之间的合法日期。
题目保证所有数据合理,即对每位老人,结算一定发生在下单之后,结算了一单才允许下另一单,且不存在只有下单没有结算、或只有结算没有下单的数据。
输出格式:
按照下单的时间顺序,输出机器人护工的分配信息,每行输出一条,格式为:
老人ID - 机器人护工编号
如果到老人结算时,都没有等到一位护工,则对应输出
老人ID - NONE
最后在一行中顺序输出队列中的机器人护工的编号。要求数字间以 1 个空格分隔,行首尾不得有多余空格。
输入样例:
3 10
a01 0 20220202
a04 1 20220103
a02 1 20220101
a05 0 20220202
a03 1 20220101
a03 0 20220202
a04 0 20220201
a02 0 20220102
a05 1 20220101
a01 1 20220101
输出样例:
a01 - 1
a02 - 2
a03 - 3
a05 - 2
a04 - NONE
1 3 2
这题的输出描述,明确强调
输出结果按下单时间序
无论老人是否分配了护工,或者没分配,都需要严格按照下单时间序来输出。
从AC的代码来看,好像并没有遵循这个原则。
官解更像遵循这个原则:
1. 老人有机器人分配,按照下单顺序输出
2. 老人无机器人分配,则按结算顺序输出
该代码源自: 2022 RoboCom 世界机器人开发者大赛-高职组(国赛)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
public class Main{
// 优化输入输出速度
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
// readLine()是获取一行数据,split(" ")是以(" ")为条件分割获取的一行数据.提高获取数据的速度.
String strings[] = br.readLine().split(" ");
// 机器人护工的数量
int N = Integer.parseInt(strings[0]);
// 系统订单信息的条数
int K = Integer.parseInt(strings[1]);
Instruct instruct[] = new Instruct[K];
for (int i = 0; i < K; i++) {
strings = br.readLine().split(" ");
instruct[i] = new Instruct(strings[0], strings[1], strings[2]);
}
// 将instruct数组按照题目要求排序,排序条件如下:
// 1.下单和结算时间不一样,则按照时间从小到大排;
// 2.下单和结算时间一样,但下单和结算不一样,结算排在前面;
// 3.下单和结算时间一样,但下单和结算一样,则按照老人ID的升序排队处理.
Arrays.sort(instruct, new Comparator<Instruct>() {
@Override
public int compare(Instruct o1, Instruct o2) {
if (o1.date.equals(o2.date)) {
if (o1.inst.equals(o2.inst)) {
return o1.ID.compareTo(o2.ID);
} else {
return o1.inst.compareTo(o2.inst);
}
} else {
return o1.date.compareTo(o2.date);
}
}
});
// 机器人护工队列.
Queue<Integer> robot = new LinkedList<Integer>();
for (int i = 1; i <= N; i++) {
robot.add(i);
}
// 等待老年人队列
Queue<String> wait = new LinkedList<String>();
// 老年人与护工映射
Map<String, Integer> map = new HashMap<String, Integer>();
// 存储输出信息
StringBuilder sb = new StringBuilder();
for (int i = 0; i < instruct.length; i++) {
String ID = instruct[i].ID;
// 下单操作.
if (instruct[i].inst.equals("1")) {
if (robot.size() != 0) {
Integer robotp = robot.poll();
map.put(ID, robotp);
sb.append(ID);
sb.append(" - ");
sb.append(robotp);
sb.append("\n");
} else {
// 将老人放入等待队列中,等待有服务机器人.
wait.add(ID);
}
} // 结算操作.
else {
if (map.containsKey(ID)) {
// 查看等待队列里有无等待的老人,无等待的老人机器人护工回队列.
if (wait.size() != 0) {
String waitp = wait.poll();
map.put(waitp, map.get(ID));
sb.append(waitp);
sb.append(" - ");
sb.append(map.get(ID));
sb.append("\n");
} else {
robot.add(map.get(ID));
}
} else {
wait.remove(ID);
sb.append(ID);
sb.append(" - NONE\n");
}
}
}
sb.append(robot.poll());
while (!robot.isEmpty()) {
sb.append(" ");
sb.append(robot.poll());
}
out.print(sb);
out.flush();
out.close();
}
}
class Instruct {
String ID;
String inst;
String date;
public Instruct(String ID, String inst, String date) {
super();
this.ID = ID;
this.inst = inst;
this.date = date;
}
}
如果构造数据
1 6
a01 1 20220101
a02 1 20220102
a03 1 20220103
a03 0 20220104
a02 0 20220105
a01 0 20220106
实际输出
a01 - 1
a03 - NONE
a02 - NONE
1
我预期的输出(需要按照下单的时间序), a02早于a03
a01 - 1
a02 - NONE
a03 - NONE
1
假如我构造类似 “(((((((…))))))))” 这样的配对数据
1 100000
a01 1 20220100+1
a02 1 20220101+2
....
a4999 1 20220100+4999
a5000 1 20220100+5000
a5000 0 20220100+5001
a4999 0 20220100+5002
...
a02 0 20220101+9999
a01 0 20220100+10000
显然可以让上述的AC
wait.remove(ID);
退化为 O ( n ) O(n) O(n), 进而整体退化为 O ( n 2 ) O(n^2) O(n2), n = 10 5 n=10^5 n=105 ,
显然官方的测试数据,没有这类case。
如果按照
那么编写的代码,能AC
#include
using namespace std;
struct T {
string name;
int op;
string at;
T(string name, int op, string at)
: name(name), op(op), at(at) {}
};
int main() {
int n, m;
cin >> n >> m;
deque<string> man;
deque<string> robot;
for (int i = 1; i <= n; i++) robot.push_back(to_string(i));
vector<T> seq;
for (int i = 0; i < m; i++) {
string name, at;
int op;
cin >> name >> op >> at;
seq.push_back(T(name, op, at));
}
sort(seq.begin(), seq.end(), [](auto &a, auto &b) {
if (a.at != b.at) return a.at < b.at;
if (a.op != b.op) return a.op < b.op;
return a.name < b.name;
});
int idx = 0;
map<string, int> hp;
int ptr = 0;
vector<array<string, 2>> res;
for (auto &e: seq) {
if (e.op == 1) {
hp[e.name] = idx++;
if (robot.empty()) {
res.push_back({e.name, ""});
}
else {
string v = robot.front(); robot.pop_front();
res.push_back({e.name, v});
cout << e.name << " - " << v << "\n";
}
} else {
int pos = hp[e.name];
string v = res[pos][1];
if (v == "") {
// pass
res[pos][1] = "NONE";
cout << res[pos][0] << " - " << "NONE" << "\n";
} else {
robot.push_back(v);
while (!robot.empty() && ptr < res.size()) {
if (res[ptr][1] != "") {
ptr++;
} else {
string v = robot.front(); robot.pop_front();
res[ptr][1] = v;
cout << res[ptr][0] << " - " << v << "\n";
ptr++;
}
}
}
}
}
while (!robot.empty()) {
string v = robot.front(); robot.pop_front();
cout << v;
cout << " \n"[robot.empty()];
}
return 0;
}
用了滑动指针,能够避免前面提到的TLE构造CASE, 不过代码的易读性确实差些。
严格按照下单时间序,来输出结果
#include
using namespace std;
struct T {
string name;
int op;
string at;
T(string name, int op, string at)
: name(name), op(op), at(at) {}
};
int main() {
int n, m;
cin >> n >> m;
deque<string> man;
deque<string> robot;
for (int i = 1; i <= n; i++) robot.push_back(to_string(i));
vector<T> seq;
for (int i = 0; i < m; i++) {
string name, at;
int op;
cin >> name >> op >> at;
seq.push_back(T(name, op, at));
}
sort(seq.begin(), seq.end(), [](auto &a, auto &b) {
if (a.at != b.at) return a.at < b.at;
if (a.op != b.op) return a.op > b.op;
return a.name < b.name;
});
int idx = 0;
map<string, int> hp;
int ptr = 0;
vector<array<string, 2>> res;
for (auto &e: seq) {
if (e.op == 1) {
hp[e.name] = idx++;
if (robot.empty()) {
res.push_back({e.name, ""});
}
else {
string v = robot.front(); robot.pop_front();
res.push_back({e.name, v});
}
} else {
int pos = hp[e.name];
string v = res[pos][1];
if (v == "") {
// pass
res[pos][1] = "NONE";
} else {
robot.push_back(v);
while (!robot.empty() && ptr < res.size()) {
if (res[ptr][1] != "") {
ptr++;
} else {
string v = robot.front(); robot.pop_front();
res[ptr][1] = v;
ptr++;
}
}
}
}
}
for (auto &e: res) {
cout << e[0] << " - " << e[1] << "\n";
}
while (!robot.empty()) {
string v = robot.front(); robot.pop_front();
cout << v;
cout << " \n"[robot.empty()];
}
return 0;
}