2022 RoboCom 世界机器人开发者大赛-高职组(国赛)RC-v2 智能陪护 详细解读 | 珂学家


前言


题解

2022 RoboCom 世界机器人开发者大赛-高职组(国赛)中的一道题

RC-v2 智能陪护

在VP的过程中,有一个case一直没过,就很苦恼。

然后网络寻找题解,2022 RoboCom 世界机器人开发者大赛-高职组(国赛)

里面是java版本,验证能AC,但查阅代码,感觉小小的脑袋充满了大大的问号。

我个人倾向于:

  1. 我理解有误
  2. 官方有误(实际标程和题意描述并不一致)
  3. 测试数据弱,放过本该TLE的代码

1,2二选一,3确定无疑,T_T

后文细细阐述

后话:

差不多累计VP了2022~2024年的所有睿抗编程赛,对个别题的题意描述,其实颇有微词,如果题目能加上对case的解读,甚至构造特定case来消除理解上歧义点,我觉得比赛体验感会更好。


RC-v2 智能陪护

智能陪护系统是一个自动为老人们分配机器人护工的系统。系统中有若干个机器人排队候选,当有老人下单时,系统自动将队列最前面的机器人派送给老人,该机器人就在老人下单的当天上岗了。当老人结算时,这个机器人就立刻结束任务,回到队列末尾排队等待下一次任务,必要时可以在当天就开始接任务。

本题就请你实现这样一个自动分配功能。

注意:如果同一天有多位老人下单或结算,保证先处理结算的,使得机器人能及时回到队列;然后处理下单——系统中另有一个下单队列,下单的老人们按照处理的顺序排队;最后分配,按下单队列中排队的顺序将机器人护工分配给老人们。

对于同时下达相同指令的,则按照老人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

歧义点

这题的输出描述,明确强调

输出结果按下单时间序

2022 RoboCom 世界机器人开发者大赛-高职组(国赛)RC-v2 智能陪护 详细解读 | 珂学家_第1张图片

无论老人是否分配了护工,或者没分配,都需要严格按照下单时间序来输出。

从AC的代码来看,好像并没有遵循这个原则。

官解更像遵循这个原则:

1. 老人有机器人分配,按照下单顺序输出
2. 老人无机器人分配,则按结算顺序输出

别人AC的代码

该代码源自: 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

如果构造数据

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

问题2

假如我构造类似 “(((((((…))))))))” 这样的配对数据

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的代码

如果按照

  1. 老人有机器人分配,按照下单顺序输出
  2. 老人无机器人分配,则按结算顺序输出

那么编写的代码,能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;
}

写在最后

你可能感兴趣的:(机器人,算法,人工智能,职场和发展,python,哈希算法)