HDU-1873 看病要排队 (优先队列)

这是一道关于模拟优先队列问题的编程题,描述了医院里医生按优先级和等待时间诊治病人的规则。题目要求根据输入的事件,模拟诊疗过程并输出每个'OUT A'事件时被诊治的病人ID。

                                      看病要排队

Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 15731    Accepted Submission(s): 6692

Problem Description

看病要排队这个是地球人都知道的常识。
不过经过细心的0068的观察,他发现了医院里排队还是有讲究的。0068所去的医院有三个医生(汗,这么少)同时看病。而看病的人病情有轻重,所以不能根据简单的先来先服务的原则。所以医院对每种病情规定了10种不同的优先级。级别为10的优先权最高,级别为1的优先权最低。医生在看病时,则会在他的队伍里面选择一个优先权最高的人进行诊治。如果遇到两个优先权一样的病人的话,则选择最早来排队的病人。

现在就请你帮助医院模拟这个看病过程。

Input

输入数据包含多组测试,请处理到文件结束。
每组数据第一行有一个正整数N(0<N<2000)表示发生事件的数目。
接下来有N行分别表示发生的

<think> 我们正在解决HDU1873看病排队”问题。根据引用内容,这是一个模拟医院排队系统的题目,涉及优先级队列(优先队列)的应用。 题目要求: - 医院有三个医生(编号1,2,3)。 - 每个病人有优先级(1-10,10最高,1最低)。 - 当医生看病时,从自己的队伍中选择优先级最高的病人。如果优先级相同,则选择先来的病人(即按时间顺序,先到先服务)。 - 操作有两种: IN A B:表示有一个病人,优先级为B,挂到医生A的队列中。 OUT A:表示医生A进行一次诊治,从自己的队列中取出优先级最高的病人并输出其编号(编号为入队时给定的顺序号,从1开始递增)。如果队列为空,则输出"EMPTY"。 解题思路: 根据题目描述,我们需要为每个医生维护一个队列,且这个队列需要能够按照优先级(从高到低)排序,当优先级相同时,按照病人到来的顺序(即编号从小到大,因为编号是递增的)排序(即先来的编号小,所以优先级高的先出队,同优先级时编号小的先出队)。 注意:优先队列通常是使用堆(heap)实现的,在C++中可以使用`priority_queue`容器。由于我们需要自定义排序规则,所以需要重载结构体的比较运算符或者自定义比较函数。 根据引用内容,有两种常见实现方式: 1. 使用优先队列(priority_queue)为每个医生创建一个队列(引用[3]和[5])。 2. 使用数组模拟队列,每次插入后排序(引用[4]),但效率较低。 我们更倾向于使用优先队列,因为这样每次插入和取出的时间复杂度较低(O(log n))。 关键点:排序规则 题目要求:优先级高的先出队,优先级相同则按到来顺序(编号小的先出队)。 在优先队列中,默认是大顶堆(即最大的元素在队首)。但是我们自定义比较规则时要注意: 我们希望优先级高的(即priority值大)排在前面,所以当比较两个病人时: - 如果优先级不同,优先级大的应排在前面(即比较时,我们认为优先级大的“小于”优先级小的?因为优先队列默认是小于号定义大顶堆?实际上在C++中,优先队列默认是vector容器,比较函数默认是less,即大的在前,但实际上是使用小于号比较,如果a<b,则b排在a前面。所以我们需要重新定义比较规则。 引用[3]中的结构体比较运算符重载: bool operator<(const node &A)const{ if(A.rank == rank) return A.id < id; // 当优先级相同时,比较id:这里的意思是,如果A.id小于当前id,则当前节点应该排在A后面?我们需要仔细分析。 return A.rank > rank; // 如果优先级不同,则优先级高的在前。这里的意思是:如果A.rank大于当前rank,则当前节点应该排在后面? } 实际上,在优先队列中,我们希望的是:优先级高的先出队,所以优先级高的应该排在队列的前面(即堆顶)。而优先队列中,我们定义的比较函数实际上是“小于”关系,即默认情况下,较大的元素在队首(因为使用less,比较时是a<b则b在a前面)。所以: 对于两个节点a和b,如果a.rank>b.rank,那么a应该排在b前面(即a<b返回false,因为a不小于b,所以a应该排在b前面?这里需要反过来理解)。 另一种方式(引用[5]): bool operator < (const myNode &o1)const { if(priority_1 == o1.priority_1) return priority_2 > o1.priority_2; // 优先级相同时,编号大的优先级低?但是我们希望编号小的先出队,所以当当前节点的priority_2(编号)大于o1的编号时,我们返回true,表示当前节点小于o1?这样o1就会排到当前节点前面?所以实际上这样定义:编号大的反而优先级低,所以不会排到前面。 else return priority_1 < o1.priority_1; // 如果当前节点的优先级小于o1的优先级,则返回true,表示当前节点小于o1,那么o1就会排到当前节点前面,所以优先级高的(数值大)就会在前面。 } 实际上,在优先队列中,元素按照从大到小排列(队首最大)。所以当我们定义比较运算符时,我们定义的是“小于”关系(即a<b为true时,a排在b的后面)。因此: 对于优先级,我们希望:如果病人的优先级越高,则它应该排在前面(即比较时,它应该“大”)。所以: - 当两个病人的优先级不同时:如果病人a的优先级大于病人b的优先级,那么a应该排在b前面。在比较函数中,如果我们使用小于号定义,则应该让a<b返回false(即a不小于b,那么a就排前面)。或者反过来,我们也可以定义成b>a然后返回,但标准做法是: 如果我们想让a排在b前面(即a>b),那么比较函数应该定义为:当a的优先级大于b的优先级,或者当优先级相同时a的编号小于b的编号(这样a先来),那么a就应该排在b前面。但是优先队列中的比较函数是“小于”函数,所以我们可以这样写: 如果a的优先级小于b的优先级,那么a应该排在b后面(所以返回true?不对) -> 重新理解: 标准做法:我们定义结构体的比较运算符重载(operator<),这个运算符用于决定在优先队列中的位置。优先队列默认是最大堆,即队首元素是最大的。所以我们需要定义:当a的优先级低于b的优先级,或者优先级相同但a的编号大于b的编号时,a应该排在b的后面(即b应该排在a的前面)。那么,在比较函数中,如果a应该排在b后面,那么a < b 应该返回true吗?不对,实际上在优先队列中,比较函数返回true表示第一个参数应该排在第二个参数后面(在堆中下沉)。所以: 我们希望:在队列中,优先级高的(数值大)在前面,同优先级时编号小的在前面(即先来的在前面)。 因此,在比较两个节点a和b时: 如果a的优先级小于b的优先级,那么a应该排在b后面,所以比较函数应该返回true(表示a小于b,所以b应该排在a前面?)但是优先队列的排序规则是:如果比较函数返回true,则第一个参数(a)将排在第二个参数(b)的后面。所以: 当a的优先级小于b的优先级时,我们返回true,这样b就会在a的前面(因为b的优先级高,我们希望b先出队)。 当a的优先级等于b的优先级时,我们比较编号:因为编号小的先来,应该先出队,所以当a的编号大于b的编号(即a后来),那么a应该排在b后面,所以返回true;反之返回false。 但是引用[3]的写法: if(A.rank == rank) return A.id < id; // 这里A是传入的参数,当前节点是this。假设我们比较的是 *this 和 A:当A.id < id(即当前节点的id大于A的id)时,返回true。那么,在优先级相同的情况下,如果当前节点的id大于A的id(即当前节点后到),那么当前节点应该排在A的后面?所以这个比较函数的意思是:如果A的id小于当前id(即A比当前节点先来),那么当前节点应该排在后面(所以返回true表示当前节点“小于”A?不对,因为比较函数是当前节点和A的比较,返回true表示当前节点应该排在A后面)。所以这个写法是正确的。 引用[5]的写法: if(priority_1 == o1.priority_1) return priority_2 > o1.priority_2; // 这里priority_2是当前节点的编号,o1.priority_2是另一个节点的编号。如果当前节点的编号大于o1的编号,返回true。那么,在优先级相同时,如果当前节点编号大于o1的编号(即当前节点后到),那么当前节点应该排在o1后面(即当前节点“小于”o1,所以返回true表示当前节点应该排在后面),这样o1就会排在前面(因为o1编号小,先来)。所以这样写也是正确的。 但是两种写法在比较优先级不同的情况: 引用[3]: return A.rank > rank; // 意思是:如果A.rank大于当前rank,则返回true。那么当A的优先级大于当前节点的优先级时,当前节点应该排在A后面(即返回true表示当前节点“小于”A,所以A应该排在当前节点前面)。正确。 引用[5]: return priority_1 < o1.priority_1; // 当前节点的优先级小于o1的优先级,则返回true。那么当前节点应该排在o1的后面(因为o1的优先级高),正确。 因此,两种写法都可以。 我们采用类似引用[5]的写法,因为更直观: 定义结构体Node,包含两个成员:优先级(prio)和编号(id)。 重载小于运算符(operator<): bool operator < (const Node & other) const { if (prio != other.prio) { return prio < other.prio; // 优先级不同,如果当前节点的优先级小于另一个节点的优先级,则返回true(表示当前节点应该排在另一个节点后面),这样另一个节点(优先级高)就会排在前面。 } else { return id > other.id; // 优先级相同,如果当前节点编号大于另一个节点的编号(即当前节点来得晚),则返回true,表示当前节点应该排在后面,所以另一个节点(来得早)排在前面。 } } 注意:在C++的优先队列中,默认使用less<Node>,即使用Node的<运算符,并且是最大堆(队首元素最大)。我们这里定义的<运算符,当a<b时,表示a应该排在b后面(即b应该在a前面),这样队首的元素就是我们想要的最大优先级(且同优先级时最小编号)的元素。 实现步骤: 1. 定义结构体Patient(或Node),包含优先级(priority)和编号(id),并重载<运算符。 2. 创建三个优先队列(分别对应三个医生),类型为priority_queue<Patient>。 3. 用一个计数器记录当前的编号(每来一个病人,计数器加1)。 4. 读取操作: 如果是"IN":读取医生编号a和优先级b,然后将一个Patient{b, 当前编号}加入医生a对应的队列,并将计数器加1。 如果是"OUT":读取医生编号a,然后判断该医生的队列是否为空: 为空:输出"EMPTY" 不为空:取出队首元素(即top()),输出其编号,然后弹出(pop())。 注意:题目要求多组测试数据。 代码实现(以C++为例): 根据引用[5]的代码稍作修改,并添加注释。 参考代码: ```cpp #include <iostream> #include <queue> #include <string> using namespace std; struct Patient { int priority; // 优先级,数值越大优先级越高 int id; // 病人编号(按到来的顺序递增) // 重载小于运算符 bool operator < (const Patient & other) const { if (priority != other.priority) { return priority < other.priority; // 优先级高的排在前面(当当前优先级小于另一个时,当前排在后面) } else { return id > other.id; // 优先级相同时,编号小的(先来的)排在前面(所以当当前id大于另一个id时,当前排在后面) } } }; int main() { int n; while (cin >> n) { // 多组测试数据 priority_queue<Patient> doctors[4]; // 下标1~3对应三个医生 int patientId = 0; // 病人编号计数器,从1开始 for (int i = 0; i < n; i++) { string op; cin >> op; if (op == "IN") { int doctorIndex, priority; cin >> doctorIndex >> priority; patientId++; Patient patient = {priority, patientId}; doctors[doctorIndex].push(patient); } else if (op == "OUT") { int doctorIndex; cin >> doctorIndex; if (doctors[doctorIndex].empty()) { cout << "EMPTY" << endl; } else { Patient patient = doctors[doctorIndex].top(); cout << patient.id << endl; doctors[doctorIndex].pop(); } } } } return 0; } ``` 注意:医生编号为1,2,3,所以我们创建了长度为4的优先队列数组(下标0不使用)。 这个程序可以满足题目要求。 其他方法(如引用[4]的数组排序)在数据量大时可能效率较低,而优先队列每次操作时间复杂度为O(log n),更优。 总结:本题主要考察优先队列(堆)的应用以及自定义比较规则。 §§相关问题§§ 1. 在HDU1873中,为什么优先队列中重载小于运算符时,优先级相同的情况下要比较病人编号? 2. 如果不使用优先队列,还有什么方法可以解决HDU1873? 3. 优先队列的时间复杂度是多少?在本题中的应用有什么优势? 4. 在C++的优先队列中,自定义比较函数还有哪些方式? 5. 如果医院有多个医生,比如100个医生,上述代码如何扩展? </think> ### HDU1873 看病排队 解题思路及代码实现 #### 题目分析 题目要求模拟医院三个医生的就诊排队系统,核心规则: 1. **优先级规则**:病情优先级分10级(10最高,1最低) 2. **排队规则**: - 医生选择队列中优先级最高的病人 - 同优先级时选择最早排队的病人(按病人编号顺序) 3. **操作指令**: - `IN A B`:病人挂到医生A的队列,优先级为B - `OUT A`:医生A接诊,输出病人编号(若队列空输出`EMPTY`) #### 解题思路 1. **数据结构选择**: - 为每个医生创建**优先队列**(最大堆) - 队列排序规则: - 第一优先级:病情级别(数值大的优先) - 第二优先级:病人编号(编号小的优先,即先到先服务) 2. **算法设计**: ```mermaid graph TD A[开始] --> B{读取操作} B --> |IN| C[获取医生ID和优先级] C --> D[生成病人编号] D --> E[压入对应医生队列] B --> |OUT| F[获取医生ID] F --> G{队列空?} G --> |是| H[输出'EMPTY'] G --> |否| I[弹出队首元素] I --> J[输出病人编号] ``` #### 代码实现(C++) ```cpp #include <iostream> #include <queue> using namespace std; struct Patient { int priority; // 病情优先级 int id; // 病人编号 // 重载小于运算符(优先队列默认最大堆) bool operator < (const Patient& other) const { if (priority == other.priority) { return id > other.id; // 同优先级时,编号小的优先 } return priority < other.priority; // 优先级高的优先 } }; int main() { int n; while (cin >> n) { priority_queue<Patient> doctors[4]; // 1~3号医生队列 int patientCount = 0; // 全局病人计数器 for (int i = 0; i < n; i++) { string op; cin >> op; if (op == "IN") { int doctorID, priority; cin >> doctorID >> priority; Patient p = {priority, ++patientCount}; doctors[doctorID].push(p); } else if (op == "OUT") { int doctorID; cin >> doctorID; if (doctors[doctorID].empty()) { cout << "EMPTY" << endl; } else { Patient p = doctors[doctorID].top(); cout << p.id << endl; doctors[doctorID].pop(); } } } } return 0; } ``` #### 关键点解析 1. **优先队列排序规则**: - 通过重载`operator<`实现自定义比较:`priority`大的优先,相同则`id`小的优先 - 注意:优先队列默认最大堆,因此实际排序方向与比较符相反[^3][^5] 2. **时间复杂度**: - 插入操作(IN):$O(\log k)$(k为队列长度) - 弹出操作(OUT):$O(\log k)$ - 优于数组排序解法($O(n^2)$)[^4] 3. **空间复杂度**: - $O(M)$,M为病人总数 - 三个队列独立存储病人 #### 扩展优化方向 1. **动态医生数量**:改用`vector<priority_queue>`可支持任意医生数量 2. **错误处理**:增加非法医生ID检测 3. **内存优化**:病人离队后及时释放内存 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值