当前位置:首页 > 技术 > 正文内容

UVa 419 会议匹配问题

访客 技术 2026年8月24日 1

题目描述

给定当前日期、需要安排的会议数量 n(n 为整数),以及每次会议的持续时间 t(t 为分钟数,且为 15 的倍数)。随后提供最多 100 个人的日程安排,每个人的日程包含若干预约,每个预约指定了日期、开始时间和结束时间(时间范围为 09:00 至 17:00)。需要找出 n 个最早的会议时间,使得所有人在该时间段内均处于空闲状态。会议时间不能与任何人的已有预约冲突,一旦某个时间被安排用于一次会议,该时间即被占用,不能用于后续会议。如果可用的会议时间少于 n 个,则输出所有可用时间,并输出一行 "No more times available"。

输入格式

第一行:当前日期,格式为 "dayname month date",其中 dayname 为单个字符(M 表示周一,T 表示周二,W 表示周三,R 表示周四,F 表示周五),month 和 date 为整数。

第二行:两个整数 n 和 t,n 表示需要安排的会议数量,t 表示每次会议的持续时间(以分钟为单位,且为 15 的倍数)。

接下来若干行:每个人的日程。每个人以一行名字开始,之后若干行表示该人的预约,每行格式为 "dayname month date start_time end_time",时间格式为 4 位数字(如 0900 表示 09:00)。每人的日程以一行 "done" 结束。

输入以一行 "done" 结束。

输出格式

输出 n 行,每行一个会议时间,格式为 "dayname month date start_time",其中 start_time 为 4 位数字。会议按日期和时间升序排列。如果可用时间少于 n 个,则输出所有可用时间后,再输出一行 "No more times available"。

样例

输入
M 8 21
2 60
Jack Casey
M 8 21 0900 1015
done
Jack Ross
M 8 21 1000 1100
M 8 21 1200 1700
done
Jack Swigert
M 8 21 1600 1700
T 8 22 0900 1000
done
done

输出
M 8 21 1100
T 8 22 1000

题目分析

本题的核心是在给定多人的日程安排中,找出所有人都空闲的连续时间段,并从中选出最早的 n 个,每次选完后将该时间段标记为已占用。

时间表示

工作时间范围为 09:00 至 17:00,共 8 小时,即 480 分钟。由于会议时间以 15 分钟为增量,可以将每天的工作时间划分为 480 / 15 = 32 个时段,每个时段对应 15 分钟。时段索引 0 对应 09:00-09:15,时段索引 31 对应 16:45-17:00。

日期范围

输入给出了当前日期,且预约不会早于当前日期,也不会超过当前日期之后一年。由于忽略闰年,一年按 365 天计算。需要遍历从当前日期开始的一年内所有工作日(周一到周五),因为周末不安排会议。

日程表示

对于每个日期,用一个长度为 32 的布尔数组(或 bitset)表示该日期每个时段是否被某人的预约占用。初始全为 false(空闲)。读取每个人的预约时,将预约覆盖的时段标记为 true(忙碌)。由于多个人的预约需要合并,最终某个时段的忙碌状态是所有人的预约在该时段的"或"结果。

寻找可用会议时间

对于每个工作日(按日期升序),扫描当天的 32 个时段,寻找连续 requiredSlots = t / 15 个空闲时段。一旦找到这样的连续时段,即得到一个可用的会议时间,起始时间即为该时段对应的时刻。将该会议占用的所有时段标记为忙碌(以便后续会议不会重复使用同一时间),然后继续寻找下一个会议。如果已找到 n 个会议,则停止。

输出

按找到的顺序输出会议时间(由于按日期升序、同一天内按时段升序扫描,输出自然有序)。如果找到的会议数少于 n,则输出所有找到的会议后,再输出一行 "No more times available"。

复杂度分析

最多考虑 365 天,每天 32 个时段,每次检查连续空闲时段需要 O(requiredSlots) 时间,总复杂度 O(365 × 32 × requiredSlots),其中 requiredSlots ≤ 32,完全可接受。

代码实现

// UVa 419 会议匹配问题
// 包含必要的头文件
#include <bits/stdc++.h>
using namespace std;

// 日期结构体,存储星期索引、月份和日期
struct Date {
    int dayIndex;  // 星期索引,0 对应周一,6 对应周日
    int month;     // 月份,从 0 开始计数(0 表示 1 月)
    int day;       // 日期,从 1 开始计数
    // 重载小于运算符,用于日期比较
    bool operator<(const Date& other) const {
        if (month != other.month) return month < other.month;
        return day < other.day;
    }
    // 重载等于运算符
    bool operator==(const Date& other) const {
        return month == other.month && day == other.day;
    }
};

// 星期字符到索引的映射
map<char, int> dayToIndex = {{'M', 0}, {'T', 1}, {'W', 2}, {'R', 3}, {'F', 4}};
// 索引到星期字符的字符串
string indexToDay = "MTWRF";

// 辅助函数:将小时和分钟转换为总分钟数
int timeToMinutes(int hour, int minute) {
    return hour * 60 + minute;
}

// 辅助函数:增加日期的天数
Date addDays(const Date& date, int days) {
    Date result = date;
    result.day += days;
    // 每个月的天数,假设非闰年
    int daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    // 处理日期溢出,调整月份
    while (result.day > daysInMonth[result.month]) {
        result.day -= daysInMonth[result.month];
        result.month++;
        result.month %= 12; // 防止月份超过 11
    }
    // 更新星期索引
    result.dayIndex = (result.dayIndex + days) % 7;
    return result;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    // 读取当前日期
    char dayChar;
    int curMonth, curDay;
    cin >> dayChar >> curMonth >> curDay;
    Date currentDate = {dayToIndex[dayChar], curMonth - 1, curDay};
    
    int n, t;
    cin >> n >> t;
    
    // 常量定义:每天时段数,开始工作小时
    const int SLOTS_PER_DAY = 32;
    const int START_HOUR = 9;
    
    // 生成一年内所有工作日(周一到周五)
    vector<Date> workDays;
    Date curDate = currentDate;
    for (int i = 0; i < 365; i++) {
        if (curDate.dayIndex < 5) { // 只考虑工作日
            workDays.push_back(curDate);
        }
        curDate = addDays(curDate, 1);
    }
    
    // 使用 map 存储每个日期的忙碌时段,键为月份和日期对,值为 bitset
    map<pair<int, int>, bitset<SLOTS_PER_DAY>> busyMap;
    
    // 忽略换行符,准备读取名字
    cin.ignore(128, '\n');
    string name;
    while (getline(cin, name)) {
        if (name == "done") break;
        // 读取该人的预约
        string line;
        while (getline(cin, line)) {
            if (line == "done") break;
            istringstream iss(line);
            char dChar;
            int month, day;
            string startStr, endStr;
            iss >> dChar >> month >> day >> startStr >> endStr;
            // 解析时间字符串
            int startHour = stoi(startStr.substr(0, 2));
            int startMin = stoi(startStr.substr(2, 2));
            int endHour = stoi(endStr.substr(0, 2));
            int endMin = stoi(endStr.substr(2, 2));
            // 计算开始和结束时段索引
            int startSlot = (startHour - START_HOUR) * 4 + startMin / 15;
            int endSlot = (endHour - START_HOUR) * 4 + endMin / 15;
            // 确保时段在有效范围内
            startSlot = max(0, startSlot);
            endSlot = min(SLOTS_PER_DAY, endSlot);
            // 标记该日期的忙碌时段
            auto dateKey = make_pair(month - 1, day);
            for (int slot = startSlot; slot < endSlot; slot++) {
                busyMap[dateKey].set(slot);
            }
        }
    }
    
    // 存储找到的会议:日期和时间(小时、分钟)
    vector<pair<Date, pair<int, int>>> meetings;
    int requiredSlots = t / 15; // 每次会议需要的时段数
    
    // 遍历每个工作日,寻找可用会议时间
    for (const Date& date : workDays) {
        auto dateKey = make_pair(date.month, date.day);
        bitset<SLOTS_PER_DAY> busy = busyMap[dateKey]; // 获取该日期的忙碌状态
        // 滑动窗口检查连续空闲时段
        for (int startSlot = 0; startSlot <= SLOTS_PER_DAY - requiredSlots; startSlot++) {
            bool available = true;
            for (int i = 0; i < requiredSlots; i++) {
                if (busy[startSlot + i]) {
                    available = false;
                    break;
                }
            }
            if (available) {
                // 计算会议开始时间
                int totalMinutes = startSlot * 15;
                int hour = START_HOUR + totalMinutes / 60;
                int minute = totalMinutes % 60;
                meetings.push_back({date, {hour, minute}});
                // 标记这些时段为已占用
                for (int i = 0; i < requiredSlots; i++) {
                    busy.set(startSlot + i);
                }
                // 如果已找到足够会议,提前结束
                if (meetings.size() >= n) break;
            }
        }
        if (meetings.size() >= n) break;
    }
    
    // 输出结果
    int outputCount = min(n, (int)meetings.size());
    for (int i = 0; i < outputCount; i++) {
        const Date& date = meetings[i].first;
        int hour = meetings[i].second.first;
        int minute = meetings[i].second.second;
        printf("%c %d %d %02d%02d\n", indexToDay[date.dayIndex], date.month + 1, date.day, hour, minute);
    }
    if (outputCount < n) {
        cout << "No more times available\n";
    }
    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。