题面
【题目描述】
尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。 尼克的一个工作日为NNN分钟,从第一分钟开始到第NNN分钟结束。当尼克到达单位后他就开始干活。如果在同一时刻有多个任务需要完成,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去写成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第PPP分钟开始,持续时间为TTT分钟,则该任务将在第P+T−1P+T-1P+T−1分钟结束。 写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。
【输入】
输入数据第一行包含两个用空格隔开的整数NNN和KKK,1≤N≤100001\leq N\leq100001≤N≤10000,1≤K≤100001\leq K\leq100001≤K≤10000,NNN表示尼克的工作时间,单位为分,KKK表示任务总数。 接下来共有KKK行,每一行有两个用空格隔开的整数PPP和TTT,表示该任务从第PPP分钟开始,持续时间为TTT分钟,其中1≤P≤N,1≤P+T−1≤N1\leq P\leq N,1\leq P+T-1\leq N1≤P≤N,1≤P+T−1≤N。
【输出】
输出文件仅一行包含一个整数表示尼克可能获得的最大空暇时间。
【样例输入】
15 6
1 2
1 6
4 11
8 5
8 1
11 5
【样例输出】
4
算法分析
对于DPDPDP题目,一种简单的设置状态的方法就是,题目怎么问,怎么设置状态。
状态:
f[i]f[i]f[i]——第iii分钟结束后获得的最大空闲时间。
状态转移方程
(1)在第iii分钟时如果没有任务:
f[i]=f[i−1]+1f[i]=f[i-1]+1f[i]=f[i−1]+1
(2)在第iii分钟有任务,选择第jjj个任务做,持续第T[j]T[j]T[j]分钟:
f[i]=max(f[i],f[i−T[j]])f[i] = max( f[i] , f[i-T[j]])f[i]=max(f[i],f[i−T[j]]),i≥T[j]i \geq T[j]i≥T[j],因为在i−T[i]+1i-T[i]+1i−T[i]+1分钟,正在开始做这个任务,所以是i−T[j]i-T[j]i−T[j]
看到这里,可能大家都发现问题了,题目说当前时刻有任务就必须要做,如果正在做任务过程中,则不做当前时刻的任务。如果我要做第jjj个任务,持续时间为T[j]T[j]T[j],我们不能确定在i−T[j]+1i-T[j]+1i−T[j]+1分钟时,是否正在做着其他任务,如果正在做其他任务,那么此时就不能做第jjj个任务。
如何解决这个问题呢?我们可以按时间逆序讨论:
状态转移方程
(1)在第i分钟时如果没有任务:
f[i]=f[i+1]+1f[i]=f[i+1]+1f[i]=f[i+1]+1
(2)在第iii分钟有任务,选择第jjj个任务做,持续第T[j]T[j]T[j]分钟:
f[i]=max(f[i],f[i+T[j]])f[i] = max( f[i] , f[i+T[j]])f[i]=max(f[i],f[i+T[j]])
这样,我就能够保证在第iii分钟开始第jjj个任务,在第i+T[j]−1i+T[j]-1i+T[j]−1分钟结束,第i+T[j]i+T[j]i+T[j]分钟又可以重新选择做哪些任务,不会有影响。
参考程序
#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 100010
using namespace std;
int n,k,f[N],s[N],p[N],t[N];
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=k;i++)
{
scanf("%d%d",&p[i],&t[i]);
s[p[i]]++; //统计当前时刻有多少个任务
}
for(int i=n;i>=1;i--) //逆推
{
if(s[i]==0) f[i]=f[i+1]+1; //当前无任务
else
{
for(int j=1;j<=k;j++)
{
if(p[j]==i) //当前任务在i时刻开始的,选择做哪一个任务
f[i]=max(f[i],f[i+t[j]]);
}
}
}
cout<<f[1]<<endl;
}