难度:普及+/提高
题目类型:动规
提交次数:1
涉及知识:线性动规
题目描述
尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。
尼克的一个工作日为N分钟,从第一分钟开始到第N分钟结束。当尼克到达单位后他就开始干活。如果在同一时刻有多个任务需要完戍,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第P分钟开始,持续时间为T分钟,则该任务将在第P+T-1分钟结束。
写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。
输入输出格式
输入格式:
输入数据第一行含两个用空格隔开的整数N和K(1≤N≤10000,1≤K≤10000),N表示尼克的工作时间,单位为分钟,K表示任务总数。
接下来共有K行,每一行有两个用空格隔开的整数P和T,表示该任务从第P分钟开始,持续时间为T分钟,其中1≤P≤N,1≤P+T-1≤N。
输出格式:
输出文件仅一行,包含一个整数,表示尼克可能获得的最大空暇时间。
代码:
1 #include<iostream> 2 #include<algorithm> 3 #include<vector> 4 using namespace std; 5 int n, k; 6 struct task{ 7 int start; 8 int last; 9 int end; 10 }; 11 bool com(task a, task b){ 12 return a.start<b.start; 13 } 14 int d[10010];//表示以第i个任务结尾的工作序列的最小消耗时间 15 int b[10010];//表示第i分钟开始的任务个数 16 int s[10010];//表示第1到i分钟的任务个数 17 int main(){ 18 cin>>n>>k; 19 int i, j; 20 vector<task>a(k+1); 21 for(i = 1; i <= k; i++){ 22 int start, last, end; 23 cin>>start>>last; 24 end = start+last-1; 25 a[i].start = start; 26 a[i].end = end; 27 a[i].last = last; 28 b[start]++; 29 } 30 sort(a.begin()+1, a.end(), com); 31 32 for(i = 1; i <= n; i++) 33 s[i] = s[i-1]+b[i]; 34 35 for(i = 1; i <=k; i++){ 36 d[i] = 10010; 37 for(j = 0; j < i; j++){ 38 if(a[j].end<a[i].start&&s[a[j].end]==s[a[i].start-1]) 39 d[i] = min(d[i], d[j]+a[i].last); 40 } 41 } 42 int ans = 10010; 43 for(i = 1; i <=k; i++){ 44 if(s[a[i].end]==s[n]) 45 ans = min(ans, d[i]); 46 } 47 cout<<n-ans<<endl; 48 49 return 0; 50 }
备注:
有思路后还是很清晰的一道题。最大空闲时间等价于求最少工作消耗时间。所以子问题是“以第i个任务为结尾的最少工作消耗时间”,在我的代码里用d[i]表示。状态怎样转移呢?引号内内容摘自洛谷题解,感觉说的非常清楚:
“把任务按照开始时间排序,以确保如果某一时刻无其他任务必须做一个。
f[i]=f[j]+cost(i) 仅当end(j)<start(i) 且 在区间(end(j),start(i))中无其他任务。
使用cnt(i)表示从1-i时刻有几个任务,如果cnt(a)==cnt(b)则说明a,b间无任务。”
一开始我居然不知道原问题的解是什么。。其实很简单啊,最后一个任务到工作时间结束之间没有其他任务开始的话,这就是一个完整的可行解。跟状态转移是一样的。
在我的代码里s数组就是回答中的cnt数组。我觉得cnt数组的设置和处理是十分巧妙的(已标黄)。它的递推过程也体现了一点动规的思想。
另外很巧妙的一点就是已标黄的“j=0”,这样一个小技巧就完美解决了在之前没有任务的情况下,最短消耗时间就是这个工作的消耗时间。
最后吐槽一点,我居然不小心在if后加了分号,并且因为这个问题查了一个小时orz