1. 程式人生 > 實用技巧 >水題挑戰4: luogu P1280 尼克的任務

水題挑戰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;
	
}