#P2655. 一种抢票系统
-
1000ms
Tried: 122
Accepted: 30
Difficulty: 5
所属公司 :
华为
时间 :2025年2月19日-留学生
-
算法标签>模拟
一种抢票系统
题解
题面描述
一个抢票系统有总票数 N(范围 [0,100000]),每个订单请求由用户 UID(8位数字)、订单号(8位数字)和所需票数(范围 [0,100])构成。当用户发起订单请求且付款成功后,其订单进入排队队列;只有当订单请求的票数不超过当前余票数时,该订单才能最终成功。
此外,系统允许在排队期间发起助力请求(即邀请其他用户帮助),助力请求进入同一队列,并可使被助力订单“前进一名”(即在最终处理顺序中上移1位)。
助力请求需满足以下规则:
- 一个用户只能帮助一次(如果重复,后续助力无效)。
- 自己不能给自己助力。
- 助力请求只能对已进入队列的订单起作用(即订单请求必须在助力请求之前到达)。
- 为防止同时处理太多请求,某个订单的助力请求必须在该订单请求后30个请求之内发出,超过30个则视为过期。
请求(订单和助力)总数不超过 10000,超过部分忽略。
处理流程为:
- 按请求到达的顺序处理订单和助力请求(模拟队列);订单请求按到达顺序入队,助力请求若满足条件,则使目标订单在队列中上移一位(如果已经处于队首则不变)。
- 最后按队列顺序依次分配票数:若当前余票大于等于订单请求的票数,则该订单成功,并扣除相应票数;否则失败。
输出时仅输出订单请求的结果,每一行格式为
UID 订单号 请求票数 success/failure
输出顺序为订单在最终队列中的顺序。
题目思路
这道题的思路是模拟一个抢票系统,通过按请求到达顺序处理订单请求和助力请求。首先,订单请求按顺序进入队列,并检查是否有足够的余票;如果有足够票数,则成功分配,否则失败。助力请求是用户帮助其他订单提前排队,助力请求只能对排在自己前面的订单有效,且一个用户只能助力一次且在订单发起后30个请求内有效。最终,我们按队列顺序处理所有订单,依次分配票数,输出每个订单请求的处理结果。核心的操作是维护一个队列,并根据助力机制动态调整队列顺序。
-
数据结构设计
- 定义一个订单结构体,保存:用户 UID、订单号、请求票数、订单请求到达的序号(用于判断助力是否超时)。
- 用一个向量(数组)模拟订单队列。
- 使用哈希表(如
unordered_map<string,int>)建立订单号到在队列中当前索引的映射,便于快速定位目标订单。 - 用一个集合记录哪些 UID 已经发起过有效助力,防止同一用户重复助力。
-
请求处理
- 按顺序读取请求(最多处理 10000 个请求,超过部分直接忽略)。
- 判断请求是订单请求(4个字段)还是助力请求(3个字段)。
- 订单请求:将订单按到达顺序加入队列,同时保存其到达序号;更新映射。
- 助力请求:
- 判断目标订单是否存在;
- 检查:助力用户与订单用户不同、该助力用户未曾助力过、且当前助力请求的到达序号与目标订单请求的到达序号之差不超过 30;
- 若满足条件,将该助力用户标记为已助力,并在队列中将目标订单与前一位订单交换(即上移 1 位),同时更新映射。
-
票数分配
- 遍历最终队列(按队列顺序),从系统总票数中依次扣除成功订单的票数,判断订单请求是否成功(票数不超过当前余票则成功,否则失败)。
-
输出
- 按最终队列顺序输出每个订单请求的 UID、订单号、请求票数及最终结果(success 或 failure)。
代码分析
- 模拟队列和映射更新
助力请求使目标订单上移 1 位。为了高效交换,在交换两个订单后需要更新它们在映射中的索引。 - 判断助力请求有效性
利用订单请求到达的序号和当前请求序号的差值判断是否超出 30;同时利用集合判断是否为重复助力。 - 票数分配
依次遍历最终订单队列,剩余票数足够则扣除、标记成功,否则标记失败。
C++ 代码实现
#include <iostream>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;
// 定义订单结构体
struct Order {
string uid; // 用户ID
string orderID; // 订单号
int ticket; // 请求票数
int arrivalIndex; // 订单请求到达的全局序号
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int totalTickets;
cin >> totalTickets;
cin.ignore(); // 去掉行尾换行
vector<Order> orders; // 模拟订单队列
unordered_map<string, int> orderIndexMap; // 订单号 -> 当前在队列中的索引
unordered_set<string> helperUsed; // 记录已使用助力的用户UID
string line;
int globalIndex = 0; // 请求全局序号(从1开始)
// 处理请求,总数不超过10000
while(getline(cin, line) && globalIndex < 10000) {
if(line.empty()) continue;
globalIndex++; // 当前请求序号
istringstream iss(line);
vector<string> tokens;
string token;
while(iss >> token) {
tokens.push_back(token);
}
// 根据 tokens 数量判断请求类型:4个为订单请求,3个为助力请求
if(tokens.size() == 4) {
// 订单请求格式: UID 请求类型(0) 订单号 请求票数
// 根据输出要求,只需保存 UID, 订单号, 请求票数 以及订单请求到达序号
Order ord;
ord.uid = tokens[0];
// tokens[1] 是请求类型(0),可以忽略
ord.orderID = tokens[2];
ord.ticket = stoi(tokens[3]);
ord.arrivalIndex = globalIndex;
orders.push_back(ord);
// 记录在队列中的位置(即当前 vector 最后位置)
orderIndexMap[ord.orderID] = orders.size() - 1;
} else if(tokens.size() == 3) {
// 助力请求格式: UID 请求类型(1) 目标订单号
// 注意:样例可能存在请求类型为0或1,但依据字段数判断
string helperUID = tokens[0];
// tokens[1] 一般为“1”,也可能样例中为“0”,但字段数为3时视为助力请求
string targetOrderID = tokens[2];
// 查找目标订单是否存在
if(orderIndexMap.find(targetOrderID) == orderIndexMap.end()) continue;
// 获取目标订单在队列中的位置
int pos = orderIndexMap[targetOrderID];
Order &targetOrder = orders[pos];
// 判断助力请求是否在订单请求后30个请求内(globalIndex - targetOrder.arrivalIndex <= 30)
if(globalIndex - targetOrder.arrivalIndex > 30) continue;
// 判断不能给自己助力
if(helperUID == targetOrder.uid) continue;
// 判断同一用户只能助力一次
if(helperUsed.find(helperUID) != helperUsed.end()) continue;
// 满足条件,记录该用户已助力
helperUsed.insert(helperUID);
// 如果目标订单已经在队列头部,则无法上移
if(pos == 0) continue;
// 向前上移一名:与前一个订单交换
int posPrev = pos - 1;
swap(orders[pos], orders[posPrev]);
// 更新交换后两个订单的映射位置
orderIndexMap[ orders[pos].orderID ] = pos;
orderIndexMap[ orders[posPrev].orderID ] = posPrev;
}
// 若 tokens 数量不为3或4,则忽略此行
}
// 票数分配:按最终队列顺序遍历,余票足够则成功,否则失败
int remaining = totalTickets;
vector<bool> isSuccess(orders.size(), false); // 存储每个订单是否成功
for (int i = 0; i < orders.size(); i++) {
if(orders[i].ticket <= remaining) {
isSuccess[i] = true;
remaining -= orders[i].ticket;
} else {
isSuccess[i] = false;
}
}
// 输出格式: "UID 订单号 请求票数 success/failure"
for (int i = 0; i < orders.size(); i++) {
cout << orders[i].uid << " " << orders[i].orderID << " " << orders[i].ticket << " "
<< (isSuccess[i] ? "success" : "failure") << "\n";
}
return 0;
}
Python 代码实现
# Python 版本实现
class Order:
def __init__(self, uid, orderID, ticket, arrivalIndex):
self.uid = uid
self.orderID = orderID
self.ticket = ticket
self.arrivalIndex = arrivalIndex
def main():
totalTickets = int(input()) # 读取总票数
orders = [] # 模拟订单队列
orderIndexMap = {} # 订单号 -> 当前在队列中的索引
helperUsed = set() # 记录已使用助力的用户UID
globalIndex = 0 # 请求全局序号(从1开始)
# 处理请求,总数不超过10000
while True:
try:
line = input().strip() # 读取一行输入
except EOFError:
break # 当没有更多输入时,跳出循环
if not line:
continue
globalIndex += 1 # 当前请求序号
tokens = line.split()
# 根据 tokens 数量判断请求类型:4个为订单请求,3个为助力请求
if len(tokens) == 4:
# 订单请求格式: UID 请求类型(0) 订单号 请求票数
uid = tokens[0]
orderID = tokens[2]
ticket = int(tokens[3])
arrivalIndex = globalIndex
ord = Order(uid, orderID, ticket, arrivalIndex)
orders.append(ord)
# 记录在队列中的位置(即当前列表最后位置)
orderIndexMap[orderID] = len(orders) - 1
elif len(tokens) == 3:
# 助力请求格式: UID 请求类型(1) 目标订单号
helperUID = tokens[0]
targetOrderID = tokens[2]
# 查找目标订单是否存在
if targetOrderID not in orderIndexMap:
continue
# 获取目标订单在队列中的位置
pos = orderIndexMap[targetOrderID]
targetOrder = orders[pos]
# 判断助力请求是否在订单请求后30个请求内
if globalIndex - targetOrder.arrivalIndex > 30:
continue
# 判断不能给自己助力
if helperUID == targetOrder.uid:
continue
# 判断同一用户只能助力一次
if helperUID in helperUsed:
continue
# 满足条件,记录该用户已助力
helperUsed.add(helperUID)
# 如果目标订单已经在队列头部,则无法上移
if pos == 0:
continue
# 向前上移一名:与前一个订单交换
posPrev = pos - 1
orders[pos], orders[posPrev] = orders[posPrev], orders[pos]
# 更新交换后两个订单的映射位置
orderIndexMap[orders[pos].orderID] = pos
orderIndexMap[orders[posPrev].orderID] = posPrev
# 票数分配:按最终队列顺序遍历,余票足够则成功,否则失败
remaining = totalTickets
isSuccess = [] # 存储每个订单是否成功
for order in orders:
if order.ticket <= remaining:
isSuccess.append(True)
remaining -= order.ticket
else:
isSuccess.append(False)
# 输出格式: "UID 订单号 请求票数 success/failure"
for i in range(len(orders)):
result = "success" if isSuccess[i] else "failure"
print(f"{orders[i].uid} {orders[i].orderID} {orders[i].ticket} {result}")
if __name__ == "__main__":
main()
Java 代码实现
import java.io.*;
import java.util.*;
// 定义订单类
class Order {
String uid; // 用户ID
String orderID; // 订单号
int ticket; // 请求票数
int arrivalIndex; // 订单请求到达的全局序号
public Order(String uid, String orderID, int ticket, int arrivalIndex) {
this.uid = uid;
this.orderID = orderID;
this.ticket = ticket;
this.arrivalIndex = arrivalIndex;
}
}
public class Main {
public static void main(String[] args) throws IOException {
// 使用 BufferedReader 提高输入效率
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String firstLine = br.readLine();
if(firstLine == null || firstLine.trim().isEmpty()){
return;
}
int totalTickets = Integer.parseInt(firstLine.trim());
List<Order> orders = new ArrayList<>(); // 订单队列
// 订单号 -> 在队列中的当前索引
Map<String, Integer> orderIndexMap = new HashMap<>();
// 记录已使用助力的 UID
Set<String> helperUsed = new HashSet<>();
String line;
int globalIndex = 0; // 请求全局序号(从1开始)
// 请求总数不超过 10000
while ((line = br.readLine()) != null && globalIndex < 10000) {
if(line.trim().isEmpty()) continue;
globalIndex++;
String[] tokens = line.trim().split("\\s+");
// 判断请求类型:4个字段为订单请求,3个字段为助力请求
if(tokens.length == 4) {
// 订单请求格式: UID 请求类型(0) 订单号 请求票数
String uid = tokens[0];
String orderID = tokens[2];
int ticket = Integer.parseInt(tokens[3]);
Order order = new Order(uid, orderID, ticket, globalIndex);
orders.add(order);
orderIndexMap.put(orderID, orders.size() - 1);
} else if(tokens.length == 3) {
// 助力请求格式: UID 请求类型(1) 目标订单号
String helperUID = tokens[0];
String targetOrderID = tokens[2];
if(!orderIndexMap.containsKey(targetOrderID)) continue;
int pos = orderIndexMap.get(targetOrderID);
Order targetOrder = orders.get(pos);
// 判断助力请求是否在订单请求后30个请求内
if(globalIndex - targetOrder.arrivalIndex > 30) continue;
// 不能给自己助力
if(helperUID.equals(targetOrder.uid)) continue;
// 同一用户只能助力一次
if(helperUsed.contains(helperUID)) continue;
helperUsed.add(helperUID);
// 如果目标订单已经在队首,则不移动
if(pos == 0) continue;
// 向前上移1位:与前一位订单交换,并更新映射
int posPrev = pos - 1;
Order prevOrder = orders.get(posPrev);
orders.set(posPrev, targetOrder);
orders.set(pos, prevOrder);
orderIndexMap.put(targetOrder.orderID, posPrev);
orderIndexMap.put(prevOrder.orderID, pos);
}
}
// 票数分配:按最终队列顺序遍历订单
int remaining = totalTickets;
// 用一个 List 存放每个订单的处理结果
List<Boolean> results = new ArrayList<>();
for (Order order : orders) {
if(order.ticket <= remaining) {
results.add(true);
remaining -= order.ticket;
} else {
results.add(false);
}
}
// 输出格式: "UID 订单号 请求票数 success/failure"
StringBuilder sb = new StringBuilder();
for (int i = 0; i < orders.size(); i++) {
Order order = orders.get(i);
sb.append(order.uid).append(" ")
.append(order.orderID).append(" ")
.append(order.ticket).append(" ")
.append(results.get(i) ? "success" : "failure").append("\n");
}
System.out.print(sb.toString());
}
}
题目内容
有一个抢票系统,给定总票数 N , N 取值范围为 [0,100000]。每个用户通过唯一的 UID 标识。
当用户发起抢票请求并完成付款时,系统会生成一个订单,订单通过唯一的订单D标识。此时用户的订单请求会进入排队队列。每个订单请求的票数取值范围为 [0,100] 当请求票数不超过当前余票数,则该订单请求成功;否则请求失败。
同一个用户支持发起多次订单请求。
系统提供一种助力排队机制,在上述订单请求排队期间如果邀请其他用户助力,该用户发起的助力系统会生成一个助力请求,助力请求也会进入上述排队队列,助力请求成功,则被助力的订单请求排队可以向前进一名;请求失败则忽略该助力请求。助力请求需要满足如下规则:一个用户只能辅助一次订单请求,重复的辅助无效;自己不能给自己辅助。每次助力请求只能帮助排在前面之前的订单请求。
系统保证所有请求按照请求到达时间顺序排序。为避免同一时间处理太多请求,某个订单的助力请求不能晚于该订单请求30个请求,晚的请求助力视作过期助力,忽略该助力。
订单请求和助力请求总数不超过 10000 。超过最大数目的请求都丢弃。
输入系统总余票数,以及订单请求、助力请求队列,请给出程序输出每个订单请求是否成功。
输入描述
第一行为余票数
后面每一行是一次订单请求的完整描述或者一次助力请求的完整描述。
订单请求的输入格式如下:
UID 请求类型 ( 0 订票) 订单 ID 请求票数
表示标识为 UID 的用户发起订单请求,订单号为订单 ID
助力请求的输入格式如下:
UID 请求类型( 1 助力) 助力订单 ID
表示标识为 UID 的用户帮助订单请求排队前进一名。
UID 为用户的唯一标识,由 8 个 0−9 数字组成;订单 ID 为订单的唯一标识,由 8 个 0−9 数字组成;请求类型取值 0或者 1 ,0 表示订单请求,1 表示助力请求。
输出描述
所有订单请求的订票结果。每一行代表一个订单请求的请求结果。
请求成功,输出:
UID 订单号 请求票数 success
请求失败,输出:
UID 订单号 请求票数 failure
样例1
输入
10
09000001 0 01000001 1
09000002 0 01000002 1
09000003 0 01000003 1
09000004 0 01000004 1
09000005 0 01000005 1
09000006 0 01000006 1
09000007 0 01000007 1
09000008 0 01000008 1
09000009 0 01000009 1
09000010 0 01000010 1
09000011 0 01000011 1
09000012 0 01000012 1
09000013 0 01000011
09000014 0 01000011
09000015 0 01000011
09000016 0 01000011
09000017 0 01000011
输出
09000001 01000001 1 success
09000002 01000002 1 success
09000003 01000003 1 success
09000004 01000004 1 success
09000005 01000005 1 success
09000011 01000011 1 success
09000006 01000006 1 success
09000007 01000007 1 success
09000008 01000008 1 success
09000009 01000009 1 success
09000010 01000010 1 failure
09000012 01000012 1 failure
说明
用户 09000011 本来排位第 11 名,但是被助力了 5 次,排名排至第 6 名,所以订票成功;
用户 09000010 本来排位第 10 名,但是因为后面的请求被助力,所以排位降至第 11 名,所以订票失败。
样例2
输入
5
09000001 0 01000001 1
09000002 0 01000002 1
09000003 0 01000003 1
09000004 0 01000004 1
09000005 0 01000005 3
09000012 1 01000005
09000013 1 01000005
09000014 1 01000005
输出
09000001 01000001 1 success
09000005 01000005 3 success
09000002 01000002 1 success
09000003 01000003 1 failure
09000004 01000004 1 failure
说明
用户 09000005 本来排位第 5 名,但是被助力了 3 次,排名排至第 2 名,所以订票成功;
用户 09000003 、09000004 排位分别降至第 4、5 位,所以订票失败。