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;
}
本文来自博客园,作者:A_box_of_yogurt,转载请注明原文链接:https://www.cnblogs.com/A-box-of-yogurt/p/18016403