Project Manager 题解

先来考虑这样一件事:对于第一个项目,某个人完成了他目前应该完成的部分,那么下一个人只要一有空就会去接手这个项目。

这一点是因为题目限定了同一个人如果在一个时刻有多种选择的话,他会选择优先级最高的项目去做。

明白了这一点的话整道题大概的思路就出来了。下面记得区分一下项目部分

现在我们不仅仅局限于第一个项目,我们可以把它依次拓展到第 $i$ 个项目的情况。

具体地,我们从第一个项目开始,每完成一个部分的时候都记录一下当前日期,一个项目的所有部分都解决了过后当前时间即为答案。开一个新的项目的时候当前时间设为 $0$。

我们依次遍历这个项目的所有部分,每次都检查这个部分的负责人,找到他晚于当前时间的最早空闲时间(这里的空闲时间是指,那一天他会参加工作,但是目前还没有被安排工作),那么这个负责人将会在这个时间去接手这一部分,并且将这个人的这段空闲时间设为已占用状态(意思是说他这段时间不能再安排别的任务了),同时要更新一下当前时间。

为什么这样做是对的?首先晚于当前时间这一点是显而易见的,如果不是选择晚于当前时间的最早空闲时间而是安排到了后面,那么这段空闲时间无论是没安排工作的或是安排给了后面的项目,肯定都不符合要求。因为我们是从第一个项目开始的,也就是说优先级是从大到小的。

现在的难点转化到了快速求出晚于当前时间的最早空闲时间

不难发现人与人之间没有关系,所以对于每一个人单独考虑。

如果不考虑假期的情况,那么我们可以把这个人的一周七天时间压缩到只有工作日的时间(就是没有休息日的情况)。

然后可以对这个工人自己的时间正常时间建立一个映射关系,要找到晚于当前时间的最早空闲时间就只需要:先找到当前时间对应到了这个人自己的时间的哪一天,然后往后推一天,在回到正常时间就好了。

并且因为这个东西具有周期性,所以求起来挺方便的。

接着加入假期的限制,如果某一天假期不在一个人的工作日里那么肯定不用考虑。虽然剩下的假期和“已经安排了工作的工作日”本质上相同,但是它们不是一个量级的(前者对于每一个人都是 $m$,后者是所有人的总和为 $k$),不能用同样的方法处理。

先讨论“已经安排了工作”的情况。

如果某个工作日是不空闲的(不能被安排工作的),但是我们却找到了这个工作日。我们显然是希望找到在这个工作日后面的空闲工作日的,所以我们希望在找到一个非空闲工作日的时候可以跳过这一天

这个操作很像并查集,但是空间肯定开不下。仔细思考一下会发现我们有很多未被更改过的结点,这些结点都是没用的,如何过滤掉?或者说,在没有更改这个结点的父亲的时候,不用申请这个结点的空间。很明显可以用 map 或者 unordered_map 代替数组。

所以某个人在某天被安排了工作过后,把他那一天的父亲指向下一天就好了。

接着是假期,我们可以不用把它当作“不能被安排工作的工作日”,而是把它看成“休息日”。具体地,我们在建立某个工人自己的时间和正常时间的映射关系时,将这些假期排除在外就好。

题目限制了“所有部分的总数量不超过 $2 \times 10^{5}$”,两只 $\log$ 都可以过,所以哪里不方便直接计算的话直接换成二分就行,具体可以看代码实现,复杂度是 $\mathcal{O}(k \log m \log V)$ 的,在“找到当前时间对应到了这个人自己的时间的哪一天”的部分用了一个二分,在“回到正常时间”的部分又套了一个二分。(因为检查的时候调用了前面的函数。)

代码还是挺短的:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m, k, p, a, now, h[200005];
vector<int> md[8];
string str;
unordered_map<string, int> mp;
struct worker {
    int t, day[7];
    unordered_map<int, int> father;
    int findset(int x) {
        if(!father.count(x)) return x;
        return father[x] = findset(father[x]);
    }
    void init() {//读入
        cin >> t;
        for(int i = 0; i < t; ++i) {
            cin >> str;
            day[i] = mp[str];
        }
    }
    int idx(int d) {//找到当前时间对应到了这个人自己的时间的哪一天
        if(d < day[0]) return 0;
        int ret = ((d + 6) / 7 - 1) * t, w = ((d + 6) / 7 - 1) * 7, pos = -1;
        for(int i = 0; i < t; ++i) {
            if(day[i] <= (d % 7 ? d % 7 : 7)) ++ret, pos = i;
        }
        if(~pos) w += day[pos];
        for(int i = 0; i < t; ++i) {
            ret -= upper_bound(md[day[i]].begin(), md[day[i]].end(), w) - md[day[i]].begin();//排除假期,原本就是休息日的就不要再排除了
        }
        return ret;
    }
    int get(int d) {
        int l = 1, r = 1000000000, mid, ret = 1;
        while(l <= r) mid = (l + r) >> 1, (idx(mid) >= d) ? (ret = mid, r = mid - 1) : (l = mid + 1);//这里直接计算很麻烦,直接无脑二分
        return ret;
    }
    int find(int bg) {
        int c = findset(idx(bg) + 1);
        father[c] = c + 1;//这一天被安排了工作,更改父亲结点
        return get(c);
    }
} w[200005];
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    mp["Monday"] = 1;
    mp["Tuesday"] = 2;
    mp["Wednesday"] = 3;
    mp["Thursday"] = 4;
    mp["Friday"] = 5;
    mp["Saturday"] = 6;
    mp["Sunday"] = 7;
    cin >> n >> m >> k;
    for(int i = 1; i <= n; ++i) w[i].init();
    for(int i = 1; i <= m; ++i) {
        cin >> h[i];
        md[h[i] % 7 ? h[i] % 7 : 7].push_back(h[i]);
    }
    while(k--) {
        cin >> p;
        now = 0;
        while(p--) {
            cin >> a;
            now = w[a].find(now);
        }
        cout << now << " ";
    }
    return 0;
}
posted @ 2023-12-20 18:52  A_box_of_yogurt  阅读(2)  评论(0编辑  收藏  举报  来源
Document