【Tai_mount】 算法学习 - 线性动态规划 - luoguP1280 尼克的任务

luoguP1280 尼克的任务

想了一个下午加半个早上,但做出来感觉十分舒爽!

本篇题解作者脑抽把分钟都写成天了,没啥大问题,就不改了。

时间复杂度:O(N+K)

dp[i]表示:若该天空闲(可以接新任务)该天之前的最大空闲时间若该天不可能空闲,则为-1

dp[i]初值均为-1(i>1),dp[1]初值为0:第一天必然空闲。(以下说的所有“空闲”都是指该天能接新任务)

设task[i][j]为:以第i天作为开始时间的第j个任务。我的程序里用数组模拟链表储存,剩下来很多空间。(所以我的代码里没有这个数组,这里为了方便表示才写的)。同时,len[i]表示第i天有len[i]个任务开始。

我们模拟的都是该天空闲的情况,若任何情况下该天都不可能空闲,dp[i]就是-1,这个点后面还会讲到。

  • 若第i天空闲:
    • 若第i天有新任务可以接:遍历各任务,任务j:dp[i+t[j]]=max(dp[i+t[j]],dp[i])
    • 若第i天没有新任务可以接:dp[i+1]=max(dp[i]+1,dp[i+1])

所求:dp[n+1]

思路说明

(真的遇到过很多问题啊)

首先我们第一个想到的就是拿子问题设dp数组。每一个状态由两个量觉得:空闲/不空闲,第几天。值设为子问题答案。

所以设dp[i][j]为第i天的最大空闲天数,j=0则当天空闲,j=1则当天不空闲。

这里为什么要设是第i天呢?我们的递推是由在某个状态下,主动更新后续的状态,而非在此状态下根据前面的信息更新自己。该状态如果空闲,还需要决策接什么任务或者没任务可接,这种情况下如果设成当天结束后的空闲时间,该状态还要考虑自己值会变化的问题,整个思路就有些乱了,不如干干净净地。这样该状态下如何决策都不会影响该状态的值。

我们发现:

  • 若第i天j=0:
    • 若无任务可以接:dp[i+1][0]=max(dp[i][0]+1,dp[i+1][0])。也就是第i天摸鱼了,空闲时间+1
    • 若有任务可以接,接了任务j,任务的持续时间为t[j]:dp[i+t[j]]=max(dp[i+t[j]],dp[i])。
  • 若第i天j=1:他能做什么呢?他什么也做不了,没法接任务的状态更新不了任何东西

审视完以后,我们发现不空闲的状态不能接任务,也并非最终答案(最终答案应该是dp[n+1][0]),所以,我要他有何用?

我们简化dp数组为:第i天空闲状况下,第i天前的最长休息时间。

然后作者就这样写了,一直没过,我们漏掉一个很重要的东西:第i天一定是在空闲状况下。

如果这一天根本就不可能空闲下来,比如第一天有两个任务,13,14,那么2,3两天一定空不下来。

我们还神奇的发现,如果第i天空闲,接了一个长度为t的任务,任务持续于i~i+t-1天的。也就是说i+t这一天一定空闲。同样的,第i天空闲又没有任务可接,i+1天也同样是空闲的。

抽象出来,我们可以建一个perm的bool数组,仅当perm为true的时候才可以由此状态更新后面的状态,perm为false就直接跳过。而某状态可以把它所更新的状态的perm都设成true。

事实上后来还能简化掉perm,因为设perm和更新dp都是去操作同一个状态,如果第i天没接任务就都是设i+1,接了任务就是设i+t。那我们直接删掉perm,把dp数组初值都赋成-1,然后dp[1]单独赋成0。第一天肯定是能接任务的。

代码实现:

#include<iostream>
#include<cstring>
using namespace std;
const int N=10007;
int n,k;
int edges[N],fst[N],nxt[N],dp[N];
void input(){//用链表储存任务,接在任务的开始时间处。edges[N]里储存持续时间,因为作者是学储存图的时候学的链表,所以习惯这样写
	cin>>n>>k;
	int p,t;
	for(int i=1;i<=k;i++){
		cin>>p>>t;
		edges[i]=t;
		nxt[i]=fst[p];
		fst[p]=i;
	}
}
void dpfun(){
	memset(dp,-1,sizeof(dp));
	dp[1]=0;
	for(int i=1;i<=n;i++){
		if(dp[i]==-1) continue;
		int f=fst[i];
		if(!f){
			dp[i+1]=max(dp[i]+1,dp[i+1]);
			continue;
		} 
		dp[i+edges[f]]=max(dp[i+edges[f]],dp[i]);
		while(nxt[f]){
			f=nxt[f];
			dp[i+edges[f]]=max(dp[i+edges[f]],dp[i]);
		}
	}
}
void output(){
	cout<<dp[n+1];
}
int main(){
	input();
	dpfun();
	output();
	return 0;
}

链表没有学过可以补一下哦~这里就不赘述了。

谢谢观看。

posted @ 2021-07-20 12:03  Tai_mount  阅读(50)  评论(0)    收藏  举报