题目大意
- 知道尼克的工作时间是n分钟,有m个任务;
- 知道每个任务的开始节点s,也知道持续时间;
- 如果同一个节点有多任务开始,必选一个;
- 如果正在做任务,无视新任务;
- 问:最大的休息时间。
题目分析
- 有一个长度为n的数轴,有m根木棍,知道木棍的起始位置s和长度;
- 同一个位置只能放一根木棍,如果一个起始位置有多根木棍可选,必须选一根;
- 问满足条件地选了木棍之后,剩余的空间尽可能大的话,是多少。
思路分析
DP的解题套路,问什么设什么:f[i]表示 从 i 时间开始,能休息的最大时间; 因为任务是有固定的开始时间和结束时间的,必然用到排序。但是排序的关键字是用s还是e呢?
思路1:从前往后,贪心
- 如果当前时间节点 i;
- 无任务,则可以多休息一分钟;
- 如果有任务可选,则选时间较少的任务。
- 此思路显然不是最优解,因为在做某个较短的任务的时候,可能错过了后面更短的任务;
DP思路2:倒推分析
- 如果从前往后是贪心,则尝试一下从后往前分析;
- 当前节点 i 最大休息的时间 f[i],与从i节点开始的任务有关:
- 如果当前没任务,f[i]=f[i+1]+1:又多休息了一秒;
- 如果当前有任务,f[i]等于任务结束的 f[e](因为做任务的时间是不能休息的);
- 只要枚举每个时间的所有任务,就可以更新出当前时间点能休息的最大值。
- 代码中用到一个非常巧妙的方法查找每个时间点的任务数量,首先任务排序按照关键字s,从大到小排序。用k表示当前询问的任务编号,因为是逆推询问,所以每次必然会刚好问完时间节点 i 开始的全部任务。
- f[1]是最终答案。
代码:
//luogu1280:尼克的任务
//解题思路:
//1 用b数组统计出,每个时间点的任务数;
//2 以开始点为关键字,对任务排序(从后往前排)
//3 从后往前扫描:
// 3.1如果当前没有任务可做,休息时间是下一秒的时间+1:f[i]=f[i+1]+1 ;
// 3.2如果当前有任务做,任务一定是对应次数的,用k来标号(这是个很巧妙的计数器);
// f[i]如果开始做任务,那么任务结束的时间的f值,就是f[i]值。
//4 f[1]是答案。
#include<bits/stdc++.h>
using namespace std;
const int mx=10005;
struct nod{int s,e;}a[mx];
int f[mx];//f[i]表示:从i时刻开始,最多休息多久
int b[mx],n,m;
bool cmp(nod x,nod y) { return x.s>y.s; }
int maxx(int x,int y) { return x>y?x:y; }
int main()
{
memset(f,0,sizeof(f));memset(b,0,sizeof(b));
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&a[i].s,&a[i].e);
a[i].e+=a[i].s;
b[a[i].s]++;//记录一个时间点开始的工作有多少个
}
sort(a+1,a+1+m,cmp);//按开始时间,从后往前排
int k=0;//记录当前要用的任务标号
for(int i=n;i>=1;i--)//从后往前扫
{
if(b[i]==0)//当前无新任务可以开始,休息
{
f[i]=f[i+1]+1;//又休息多了1秒
}
else//i时刻有新任务,比较选择哪个任务
{
for(int j=1;j<=b[i];j++)
{
f[i]=maxx(f[a[++k].e],f[i]);//比较选择哪个任务,能休息更多
}
}
}
printf("%d",f[1]);
return 0;
}