水題挑戰4: luogu P1280 尼克的任務
題目描述
尼克每天上班之前都連線上英特網,接收他的上司發來的郵件,這些郵件包含了尼克主管的部門當天要完成的全部任務,每個任務由一個開始時刻與一個持續時間構成。
尼克的一個工作日為 \(n\) 分鐘,從第 \(1\) 分鐘開始到第 \(n\) 分鐘結束。當尼克到達單位後他就開始幹活,公司一共有 \(k\) 個任務需要完成。如果在同一時刻有多個任務需要完成,尼克可以任選其中的一個來做,而其餘的則由他的同事完成,反之如果只有一個任務,則該任務必需由尼克去完成,假如某些任務開始時刻尼克正在工作,則這些任務也由尼克的同事完成。如果某任務於第 \(p\) 分鐘開始,持續時間為 \(t\) 分鐘,則該任務將在第 \((p+t−1)\)
寫一個程式計算尼克應該如何選取任務,才能獲得最大的空暇時間。
輸入格式
輸入資料第一行含兩個用空格隔開的整數 \(n\) 和 \(k\)。
接下來共有 \(k\) 行,每一行有兩個用空格隔開的整數 \(p\) 和 \(t\),表示該任務從第 \(p\) 分鐘開始,持續時間為 \(t\) 分鐘。
輸出格式
輸出檔案僅一行,包含一個整數,表示尼克可能獲得的最大空暇時間。
輸入輸出樣例
輸入 #1
15 6
1 2
1 6
4 11
8 5
8 1
11 5
輸出 #1
4
說明/提示
資料規模與約定
對於 \(\%100\) 的資料,保證 \(1 \leq n \leq 10^4,1 \leq k \leq 10^4,1 \leq p \leq n,1 \leq p+t-1 \leq n\)
solution
又雙叒叕來刷\(DP\)水題難題了
一開始我從任務排序之後來做 \(DP\), 經歷現實的毒打之後就放棄了這種\(naive\)的想法。
於是我們考慮直接從時間角度來遞推:
考慮 \(f[i]\) 表示從第 \(i\) 分鐘到末尾的最大休息時間。
對於每一個任務,我們記錄它們的起始時間,這個起始時間可以從它的結束時間轉移而來。
而如果在某個時間沒有起始任務,就可以先當成尼克在這個時間點進行休息。
於是乎我們有如下 \(DP\):
\(\left\{\begin{array}\\f[i-1]+1,start[i]==0\\f[i + t[j]],j\in start[i] \end{array}\right.\)
\(start[i]\) 表示從時間點\(i\) 開始的任務集合。但其實記錄的時候只需要記下個數就好,我們拍好順序從後往前推便可以求得哪些工作是以\(i\) 為起始時間。
Code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define repd(i,a,b) for(int i=a;i>=b;i--)
#define _(d) while(d(isdigit(ch=getchar())))
template <class T> void g(T&t){T x,f=1;char ch;_(!)ch=='-'?f=-1:f;x=ch-48;_()x=x*10+ch-48;t=f*x;}
const int N=1e4+4;
struct A{
int l, t;
bool operator<(const A rhs)const{
return l > rhs.l;
}
}a[N];
int n, k, f[N], s[N];
int main(){
g(n), g(k);
rep(i, 1, k){
g(a[i].l), g(a[i].t);
s[a[i].l]++;
}
sort(a+1, a+1+k);
int id=1;
repd(i, n, 1){
if( !s[i] ) f[i]=f[i+1] + 1;
else{
rep(j, 1, s[i]){
f[ i ]=max( f[ i ], f[ i + a[id].t ] );
id ++;
}
}
}
printf("%d\n",f[1]);
return 0;
}