1. 程式人生 > 實用技巧 >講義 GDFZOJ 【38】 動態規劃基礎3

講義 GDFZOJ 【38】 動態規劃基礎3

更好的閱讀體驗

上樓梯問題

這是一個很水的題目,就是一個斐波那契數列……

設有 \(f\) 陣列,則 \(f_i=f_{i-1}+f_{i-2}(f_1=1,f_2=2)\)

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int N=35;
int f[N]={0,1,2};
int main() {
	int n;
	scanf("%d",&n);
	for(int i=3; i<=n; i++) {
		__________
	}
	__________
	return 0;
}

乘車與購票

如果要抵達距離為 \(x\) 的一個點,可以從 \(x-y\) 的地方在乘一輛距離為 \(y\) 的車。

於是有狀態轉移方程:\(f_i=\max\{f_{i-j}+a_j\}\) 。其中 \(a\) 陣列為 \(10\) 公里及以內的車費。

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int f[N],a[N];
int main() {
	________
	for(int i=1; i<11; i++) {
		scanf("%d",a+i);
	}
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		for(int j=1; j<11; j++) {
			________        	
		}
	}
	printf("%d",f[n]);
	return 0;
}

小豬過河

我們可以讓這隻小豬從 \(0\) 號節點開始跳,跳到 \(n+1\) 號節點。

則有方程:$ f_i=\max(f_i,f_{i-j}-q+a_i),1\le j\le2$ 。

再判斷一下在跳的過程中的體力值即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int f[N],a[N];
int main() {
	int p,q;
	scanf("%d %d",&p,&q);
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",a+i);
	}
	f[0]=p,f[1]=p-q>=0?p-q+a[1]:-1e9;
	for(int i=2; i<=n+1; i++) {
		int flag=0;
		________
	}
	if(f[n+1]<=0) {
		printf("NO");
	} else {
		printf("%d",f[n+1]);
	}
	return 0;
}

排隊買票

我們可以發現:一個人可以自己買票,也可以和他後面的人買票。

所以有方程:\(f_i=\max(f_{i-1}+a_i,f_{i-1}+b_{i-1})\)

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int f[N],a[N],b[N];
int main() {
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",a+i);
	}
	for(int i=1; i<n; i++) {
		scanf("%d",b+i);
	}
	________
	printf("%d",f[n]);
	return 0;
}

護衛隊

\(f_i\) 為前 \(i\) 輛車通過橋的最短時間,設 \(t_{i,j}\)\(i\) -第 \(j\) 輛車(租車的車隊)通過所需時間。

我們分析方程:

來源

綜合起來,我們就得到了方程:\(f_i=\min(f_{j-1}+t_{j,i})\)且要滿足 \(\sum_{k=j}^iw_k\le t\)

為了降低時間複雜度、程式設計複雜度,我們可以:

  1. 預處理重量

    定義 \(W_i\) 為前 \(i\) 輛車的總重(即字首和,是處理這類問題非常好的方法,可以降低時間複雜度)。需要知道第 \(j\) 輛車到第 \(i\) 輛車的總重時,用\(W_i-W_{j-1}\)就可以了;

  2. 預處理時間

    定義 \(t\) 為橋的全長; \(v_i\) 為第 \(i\) 輛車的速度; \(T_i\) :第 \(i\) 輛車通過橋所需時間; \(t_{i,j}\)\(i\) 輛車到第 \(j\) 輛車組成的車隊通過橋所需的時間;

  3. \(w\) 陣列、\(v\) 陣列和\(W\) 陣列一定要開 long long,否則兩個較大的 int 相加會炸掉! (雖然此題可能沒有卡 int)。

程式碼:

#include<bits/stdc++.h>
using namespace std;
using LL=long long;
const LL N=1005;
LL w[N],s[N],sum[N];
double f[N],T[N],a[N][N];
int main() {
	LL t,l,n;
	scanf("%lld %lld %lld",&t,&l,&n);
	for(LL i=1; i<=n; i++) {
		scanf("%lld %lld",w+i,s+i),sum[i]=sum[i-1]+w[i],a[i][i]=T[i]=(double)l/s[i]*60;
	}
	for(LL i=1; i<=n-1; i++) {
		for(LL j=i+1; j<=n; j++) {
			a[i][j]=max(a[i][j-1],T[j]);
		}
	}
	for(LL i=1; i<=n; i++) {
		f[i]=T[i]+f[i-1];
		________
   	}
	printf("%.1lf",f[n]);
	return 0;
}

工作安排

\(f_i\) 為前 \(i\) 分鐘能獲得的最大的經驗值。

預設在工作後才能獲得經驗值。容易知道,對於每一個工作,完成時間的 \(f\) 值就等於開始時間的 \(f\) 值加上工作所獲得的經驗值(裡面取個最大值),即 \(f_{S_i+E_i}=max(f_{S_i+E_i},f_{S_i}+P_i)\)

但是要注意,對於第 \(i\) 分鐘,它可能不是某個工作的結束時間,即 \(f_i\) 不會被更新。所以在列舉完前一個的結束狀態時,就要用 \(f_j=\max(f_j,f_{j-1})\) 對結果進行遞推。

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
struct work {
	int s,e,p;
	void read() {
		scanf("%d %d %d",&s,&e,&p);
		e+=s;
	}
	bool operator<(const work &x) const {
		________
	}
} a[N];
int f[N];
int main() {
	int n,m;
	scanf("%d\n%d",&m,&n);
	for(int i=1; i<=n; i++) {
		a[i].read();
	}
	sort(a+1,a+n+1);
	________
	int ans=0;
	for(int i=1; i<=m; i++) {
		ans=max(ans,f[i]);
	}
	printf("%d",ans);
	return 0;
}

最長上升子序列

首先我們來講解一下他的遞推關係式:\(f_i=\max(f_i,f_j+1)\)

定義 \(f_i\) 為:以 \(a_i\) 為末尾的最長上升子序列的長度。

那麼 \(f_i\) 包含什麼呢?

情況\(1\):只包含它自己,也就是說它前面的元素全部都比他大;

情況2:為了保證上升子序列儘可能的長,那麼就有 \(f_i\) 儘可能的大,但是再保證 \(f_i\) 儘可能大的基礎上,還必須滿足序列的上升。所以 \(f_i=\max(1,f_j+1)(j<i,a_j<a_i)\) 。這裡的 \(1\) 就是當 \(a_i\) 前面的數都比他大的時候,他自己為一個子序列;\(f_j+1\) 指的是: 當第 \(i\) 個數前面有一個第 \(j\) 個數滿足 \(a_j<a_i\) 並且 \(j<i\) 這時候就說明 \(a_i\) 元素可以承接在 \(a_j\) 元素後面來儘可能的增加子序列的長度。

\(j\)\(1\) 遍歷到 \(i-1\) ,在這之間,找出儘可能大的 \(dp_i\)即為最長上升子序列的長度。(注意: \(f_n\) 不一定是最長的子序列。)

來源

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int a[N],f[N];
int main() {
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",a+i),f[i]=1;
	}
	int ans=0;
	________
	printf("%d",ans);
	return 0;
}

合唱隊形

顯然這是一個最長上升子序列最長下降子序列有機結合。

求出兩者可連線的數量的最大值,那 \(n\) 去減即可。

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int N=205;
int a[N],f[2][N];
int main() {
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",a+i),f[1][i]=f[0][i]=1;
	}
	________
	int ans=0;
	for(int i=1; i<=n; i++) {
		ans=max(f[0][i]+f[1][i]-1,ans);
	}
	printf("%d",n-ans);
	return 0;
}

友好城市

將兩岸的友好城市用一個結構體存起來,以其中一個城市作為關鍵字進行排序,那麼之後另一個城市其實會呈最長上升子序列,例如 \(A\) 岸城市 \(1\)\(A\)岸城市 \(1\) 的意思是距離河的起點為 \(1\)\(A\) 岸城市)和 \(B\) 岸城市 \(3\) 之間如果開通航線,那麼以後的城市都不可能向 \(B\) 岸城市 \(1\)\(2\)\(3\) 開通航線。那麼也就呈最長上升子序列狀,只要套用模板求出最長即可。

注意:一定要排序

來源

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
struct line {
	int c1,c2;
	________
	void read() {
		scanf("%d %d",&c1,&c2);
	}
} a[N];
int f[N];
int main() {
	int x,y;
	scanf("%d %d",&x,&y);
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		a[i].read(),f[i]=1;
	}
	sort(a+1,a+n+1);
	________
	int ans=0;
	for(int i=1; i<=n; i++) {
		ans=max(ans,f[i]);
	}
	printf("%d",ans);
	return 0;
}

後記

  1. 感謝 L_Z_Y 對此文章提出的建議,並幫助我寫了 工作安排 這一塊!

  2. 本文有彩蛋

  3. 本文由 Z_Z_R 編寫,zhnzh 轉載與此。