1. 程式人生 > 其它 >【Tai_mount】 演算法學習 - 線性動態規劃 - luoguP1280 尼克的任務

【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;
		if(!fst[p]){
			fst[p]=i;
			continue;
		}
		int f=fst[p];
		while(nxt[f]){
			f=nxt[f];
		}
		nxt[f]=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;
}

連結串列沒有學過可以補一下哦~這裡就不贅述了。

謝謝觀看。