講義 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\) 。
為了降低時間複雜度、程式設計複雜度,我們可以:
-
預處理重量
定義 \(W_i\) 為前 \(i\) 輛車的總重(即字首和,是處理這類問題非常好的方法,可以降低時間複雜度)。需要知道第 \(j\) 輛車到第 \(i\) 輛車的總重時,用\(W_i-W_{j-1}\)就可以了;
-
預處理時間
定義 \(t\) 為橋的全長; \(v_i\) 為第 \(i\) 輛車的速度; \(T_i\) :第 \(i\) 輛車通過橋所需時間; \(t_{i,j}\) 第 \(i\) 輛車到第 \(j\) 輛車組成的車隊通過橋所需的時間;
-
\(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;
}
後記
-
感謝 L_Z_Y 對此文章提出的建議,並幫助我寫了 工作安排 這一塊!
-
本文有彩蛋
-
本文由 Z_Z_R 編寫,zhnzh 轉載與此。