[P1280] 尼克的任务 题解
题目描述
尼克要完成 \(k\) 个任务。尼克的一个工作日为 \(n\) 分钟,从第 \(1\) 分钟开始到第 \(n\) 分钟结束。公司一共有 \(k\) 个任务需要完成。如果在同一时刻有多个任务需要完成,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第 \(p\) 分钟开始,持续时间为 \(t\) 分钟,则该任务将在第 \((p+t-1)\) 分钟结束。求最大空闲时间。
解法
一看就知道这是一个 dp,但是问题是我已经复习一周 dp 了但是还是不会写 dp。
于是考虑采用其他的方法来做。我们把每个分钟初的时刻看作一个点。那么对于一个开始时刻为 \(s_i\),持续时间为 \(l_i\) 的任务,我们将编号为 \(s_i\) 的点向编号为 \((s_i + l_i)\) 的点连一条边权为 \(0\) 的有向边。同时,如果一个时刻没有任务开始,那么向下一个时刻所对应的点连一条边权为 \(1\) 的有向边。
这样的一个图表明,如果你当前有任务,那么必须选择一个任务做。如果没有任何任务,那么你就可以有从这一分钟到下一分钟的休息时间,那么休息时间的计数增加。
我们可以发现,这是一个有向无环图,因为一个时间是不可逆的。那么问题就转换成了在 DAG 上求最长路。
由于我很不想使用 dijkstra 来写最长路,同样的也是为了复习,我写了一个 spfa。具体方法是把所有的边权取相反数,然后在这个负权图上求最短路,将最后的答案再取一个反。
代码实现
建立结构体储存每一个人物,对任务进行按照开始时间排序。那之后,顺序扫描每一个点,对于每一个点在所有任务里以这个点为起始点的任务建边。这个过程可以用另一个指针来完成,因为排序过从左到右直接扫一遍即可。如果没有建任何边,那么向下一个时刻所对应的点连一条边权为 \(-1\) 的有向边。
然后跑 spfa。如果有兴趣可以将这一部分改成稳定的 dijkstra。最后取 \(dis_{n+1}\)。为什么呢?原因是植树问题。\(n\) 分钟有 \(n+1\) 个时刻。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int maxn = 1e4 + 5;
const int inf = 0x3f3f3f3f;
struct Work {
int st, ed;
friend bool operator < (Work a, Work b) {
if(a.st != b.st) return a.st < b.st;
else return a.ed < b.ed;
}
} a[maxn];
struct E {
int nxt, to, w;
} e[maxn << 2];
int n, k, head[maxn], cnt, dis[maxn];
bool vis[maxn];
void add(int x, int y, int z) {
e[++ cnt].to = y;
e[cnt].w = z;
e[cnt].nxt = head[x];
head[x] = cnt;
}
queue<int> q;
void spfa() {
for(int i = 1; i <= n; i ++)
dis[i] = inf;
dis[1] = 0;
vis[1] = 1;
q.push(1);
while(!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for(int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if(dis[u] + e[i].w < dis[v]) {
dis[v] = dis[u] + e[i].w;
if(!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
}
int main() {
cin >> n >> k;
for(int i = 1; i <= k; i ++) {
int c, d;
cin >> c >> d;
a[i].st = c, a[i].ed = c + d;
}
sort(a + 1, a + 1 + k);
int pt = 0;
for(int i = 1; i <= n; i ++) {
bool flg = false;
if(pt <= k) {
while(a[++ pt].st == i) {
add(i, a[pt].ed, 0);
flg = true;
}
pt -= 1;
}
if(!flg) add(i, i + 1, -1);
}
spfa();
cout << -dis[n + 1] << endl;
}