[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;
}
posted @ 2022-09-15 16:52  Inversentropir-36  阅读(46)  评论(0编辑  收藏  举报