luogu1280:尼克的任务:线段最小覆盖问题

题目连接

  • 该题是luogu试炼场的2-16:T3

题目大意

  1. 知道尼克的工作时间是n分钟,有m个任务;
  2. 知道每个任务的开始节点s,也知道持续时间;
  3. 如果同一个节点有多任务开始,必选一个;
  4. 如果正在做任务,无视新任务;
  5. 问:最大的休息时间。

题目分析

  • 线性dp的经典线段最小覆盖问题,等价于以下逻辑:
  1. 有一个长度为n的数轴,有m根木棍,知道木棍的起始位置s和长度;
  2. 同一个位置只能放一根木棍,如果一个起始位置有多根木棍可选,必须选一根;
  3. 问满足条件地选了木棍之后,剩余的空间尽可能大的话,是多少。


思路分析

  • DP的解题套路,问什么设什么:f[i]表示 从 i 时间开始,能休息的最大时间;
  • 因为任务是有固定的开始时间和结束时间的,必然用到排序。但是排序的关键字是用s还是e呢?
思路1:从前往后,贪心
  1. 如果当前时间节点 i;
  2. 无任务,则可以多休息一分钟;
  3. 如果有任务可选,则选时间较少的任务。
  4. 此思路显然不是最优解,因为在做某个较短的任务的时候,可能错过了后面更短的任务;
DP思路2:倒推分析
  1. 如果从前往后是贪心,则尝试一下从后往前分析;
  2. 当前节点 i 最大休息的时间 f[i],与从i节点开始的任务有关:
  3. 如果当前没任务,f[i]=f[i+1]+1:又多休息了一秒;
  4. 如果当前有任务,f[i]等于任务结束的 f[e](因为做任务的时间是不能休息的);
  5. 只要枚举每个时间的所有任务,就可以更新出当前时间点能休息的最大值。
  6. 代码中用到一个非常巧妙的方法查找每个时间点的任务数量,首先任务排序按照关键字s,从大到小排序。用k表示当前询问的任务编号,因为是逆推询问,所以每次必然会刚好问完时间节点 i 开始的全部任务。
  7. 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
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;
}



你可能感兴趣的:(题解,DP,luogu,大礼包)