a567: 死線排程

Tags: greedy, scheduling


出處:https://zerojudge.tw/ShowProblem?problemid=a567
提交:https://zerojudge.tw/Submissions?problemid=a567&account=allllllan123456


問題:給定 $N\in[1,10000]$ 項工作,每項工作有各自的整數死線日期 $d_i\in[1,10000]$ 和利益 $p_i\in[1,32766]$,每項工作都可以在第 1 到 $d_i$ 天的其中一天內完成,但每天最多只能進行一項工作。請問如何排程能得到總利益的最大值?求此最大值。


解法:這題也是很經典的 greedy 演算法,先把所有工作按照利益由大到小排序,接著按照此順序依序安排執行日期,每項工作安排的方式是由自己的死線開始看,如果有空位就放,沒空位就往前一格繼續看,直到有空位放為止,如果都沒有空位放就只能放棄這項工作,那麼每項工作都考慮過之後就算是安排完畢。


證明:既然是 greedy 演算法,就要用標準方式證明。現在我們想說明每次對於尚未決定好安置日期之最大利益工作 $w_i$ 的安排方式,上述方法絕不會錯,只要說明其他方法都不優於上述方法,上述方法就是最佳選擇。先列出對於 $w_i$ 的三種可能選擇,選項 (1) 安排在死線 $d_i$ 之前 (含死線當天) 但最靠近死線的空位 (姑且稱為 X),選項 (2) 安排在死線 $d_i$ 之前但放在 Y,注意到 Y $\ne$ X,選項 (3) 直接放棄此工作。先討論選項 (2),假設此選項現在這個時候也能獲得局部最佳解,那麼我必定可以把目前放在 Y 的 $w_i$ 改移至 X,如果在改移之前 X 上面剛好又有其他工作 $w_j$ 的話,它必定也可以改放在 Y 上面,如此一來就能說明選項 (2) 的總利益也能由選項 (1) 達成。再來討論選項 (3),選項 (3) 和選項 (1) 的差別在於少了一個 $w_i$ 的可能性,對於任何一個選項 (3) 的安置方式,我勢必可以把它最前面的空位 (無論是否已有工作占據) 取代為 $w_i$,如此一來便說明了選項 (3) 的局部最佳解不可能優於選項 (1) 或選項 (2)。綜合上述,我們便可以推得選項 (1) 必定為最佳選項,一路按照此要領操作下去便能得到最優安排。


 1#include <bits/stdc++.h>
 2using namespace std;
 3
 4int n, deadline[10000], ids[10000], profit[10000], task[10001];
 5
 6int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); // IO 優化
 7    while (cin >> n) {
 8        int maxDL = -1;
 9        for (int i=0; i<n; i++) {
10            cin >> deadline[i] >> profit[i];
11            if (deadline[i] > maxDL) maxDL = deadline[i]; // 更新最久遠的 deadline
12            ids[i] = i;
13        }
14        memset(task+1, 0, maxDL*sizeof(int)); // 初始化每個日期被完成的 task 的利益
15        sort(ids, ids+n, [](int a, int b) { return profit[a] > profit[b]; });
16        for (int i=0; i<n; i++) {
17            int id = ids[i];
18            int day = deadline[id];
19            while (day && task[day]) day--;
20            if (day) task[day] = profit[id];
21        }
22        cout << accumulate(task+1, task+maxDL+1, 0) << '\n';
23    }
24    return 0;
25}

no image