AHU算法课-DP动态规划
生活随笔
收集整理的這篇文章主要介紹了
AHU算法课-DP动态规划
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
A-最長上升子序列
入門dp題,之前寫過這道題的題解 點擊進入
這道題出的很簡單,O(n2)就可以過。但請思考如何降低復雜度,本題可以將復雜度下降到O(nlogn) , 方法是:因為是最長上升子序列,具有著單調性,所以通過二分查找(O(logn))代替原來的遍歷查找(O(n))。這樣復雜度就下降到了O(nlogn)。
O(nlogn)解法
O(n2)解法:
#include <bits/stdc++.h> using namespace std; int a[15000],f[15000]; int n,ans=-1; int main() {scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);f[i]=1;}for(int i=1;i<=n;i++)for(int j=1;j<i;j++)if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);for(int i=1;i<=n;i++)ans=max(ans,f[i]);printf("%d\n",ans);return 0; }B - 數字三角形
從上往下推,第i層第t列的由第i-1層第t-1列和第i-1層第t列轉移,轉移方程:
f[i][t]=a[i][t]+max(f[i-1][t],f[i-1][t-1]);
#include <stdio.h> #include<stdlib.h> #include<string.h> #include<algorithm> using namespace std; int main() {int n;scanf("%d",&n);int a[105][105];int f[105][105];//memset(f,0,sizeof f);for(int i=1; i<=n; i++){for(int t=1; t<=i; t++){scanf("%d",&a[i][t]);//f[i][t]=0;}}f[1][1]=a[1][1];for(int i=2;i<=n;i++){for(int t=1;t<=i;t++){if(t==1){f[i][t]=a[i][t]+f[i-1][t];}else f[i][t]=a[i][t]+max(f[i-1][t],f[i-1][t-1]);}}int maxx=0;for(int i=1;i<=n;i++){//printf("%d ",f[n][i]);maxx=max(f[n][i],maxx);}printf("%d",maxx);return 0; }C-01背包
dp中的經典題,對于每一件物品,選擇拿或者是不拿這就是dp的狀態轉移方式。
轉移方程:dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]); (i為第i件物品,j為當前背包還剩的容量)
代碼:
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> using namespace std; int dp[3409][12885]; int w[3409],v[3409]; int main() {int n,vmax;scanf("%d%d",&n,&vmax);memset(dp,0,sizeof dp);for(int i=1;i<=n;i++){scanf("%d%d",&w[i],&v[i]);}for(int i=1;i<=n;i++){for(int j=0;j<=vmax;j++){if(j-w[i]<0){dp[i][j]=dp[i-1][j];}else {dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);}}}int maxx=0;for(int i=1;i<=vmax;i++){maxx=max(maxx,dp[n][i]);}printf("%d",maxx);return 0; }可惜的是上面的代碼會MLE,這時候就要對其進行空間優化了,在下面這幾行代碼中,我們知道,dp的轉移只和當前層與上一層有關,那么dp用一個二維數組儲存是會造成空間的大量浪費,那么只用一維數組進行優化
for(int i=1;i<=n;i++){for(int j=0;j<=vmax;j++){if(j-w[i]<0){dp[i][j]=dp[i-1][j];}else {dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);}}}ac代碼
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> using namespace std; int dp[12889]; int w[3409],v[3409]; int main() {int n,vmax;scanf("%d%d",&n,&vmax);memset(dp,0,sizeof dp);for(int i=1;i<=n;i++){scanf("%d%d",&w[i],&v[i]);}for(int i=1;i<=n;i++){for(int j=vmax;j>=0;j--){if(j>=w[i]) {dp[j]=max(dp[j-w[i]]+v[i],dp[j]);}}}int maxx=0;for(int i=1;i<=vmax;i++){maxx=max(maxx,dp[i]);}printf("%d",maxx);return 0; }D - 完全背包
完全背包與01背包的區別之處在于,完全背包問題中每個物品種類有無窮多個。
1. 首先大家一定會想到這種方式:和01背包一樣,只不過我在每次選某種物品時,對其的數量選的數量進行遞增跑一遍。
狀態轉移方程: f[i][j] = max(f[i][j],f[i-1][j - k * c[i]] + k * w[i]) (0<=k*c[i]<=v)
2.然而這種方法復雜度略高,因為這樣不得不進行三次for循環(枚舉哪種物品,當前背包容量,對于這種物品選幾個)
我們再進行優化,改變一下dp思路
我們可以把把完全背包問題轉化為01背包問題來解,第i種物品最多選V/c[i]件,于是可以把第i種物品轉化為v/c[i]件費用及價值均不變的物品,然后求解這個01背包問題。
即:將一種物品拆成多件物品。
我們現在dp每一個物品,dp出該種物品在不同剩余容量下的最優解,他是以每1個為單位的。考慮是否在當前所有物品總數中添加一件新的該物品
我們用i代表前i種物品,v代表包的最大承重,c[i]是第i種物品消耗的空間、w[i]是第i種物品的價值、f[i,j]是最大價值(從前i種物品取若干件放入有j個剩余空間的包)。
如果不放那么f[i][j]=f[i-1][j]
如果確定放,那么f[i][j]=f[i][j-c[i]+w[i]],為什么會是f[i][j-c[i]]+w[i]?
因為我們要考慮的是在當前基礎上添加一件物品i。
就是說如果你放第i種物品,并不牽扯到第i-1種物品,所以不管你放多少件,都要在第i種商品的基礎上操作
所以說遞推式為:
f[i][j]=max(f[i-1][j],f[i][j-c[i]]+w[i])
3.還能繼續優化嗎? 我們還可以利用一個滾動數組,想01背包那樣對空間進行優化。
我們先回顧01背包為什么寫1維要逆序?
因為為了避免要使用的子狀態收到影響。
那我們該如何寫完全背包的1維優化呢?
答案是:順序
因為第i種物品一旦出現,原來沒有第i種物品的情況下可能有一個最優解,現在第i種物品 出現了,而它的加入有可能得到更優解,所以之前的狀態需要進行改變,故需要正序。
所以說遞推式是這樣子的:
f[j] = max(f[j],f[j-c[i]]+w[i])
#include<cstring> #include<cstdio> #include<algorithm> #define inf 1000000000 using namespace std; int w[10005],v[10005],dp[10005]; int main() {int repeat;scanf("%d",&repeat);while(repeat--){int x,y,n,i,j;memset(w,0,sizeof(w));memset(v,0,sizeof(v));scanf("%d%d",&x,&y);int W=y-x;for(i=0;i<=W;i++)dp[i]=inf;dp[0]=0;scanf("%d",&n);for(i=0;i<n;i++){scanf("%d%d",&v[i],&w[i]);}for(i=0;i<n;i++){for(j=w[i];j<=W;j++){dp[j]=min(dp[j],dp[j-w[i]]+v[i]);}}if(dp[W]<inf)printf("The minimum amount of money in the piggy-bank is %d.\n",dp[W]);else printf("This is impossible.\n");}return 0; }E-區間DP
區間dp就是在區間上進行動態規劃,求解一段區間上的最優解。主要是通過合并小區間的 最優解進而得出整個大區間上最優解的dp算法。
for(int len = 1;len<=n;len++){//枚舉長度for(int j = 1;j+len<=n+1;j++){//枚舉起點,ends<=nint ends = j+len - 1;for(int i = j;i<ends;i++){//枚舉分割點,更新小區間最優解dp[j][ends] = min(dp[j][ends],dp[j][i]+dp[i+1][ends]+something);//dp[j][ends]的意思是從j到ends和;}}}自己找一個例子對著上面的板子模擬一遍就知道其中意義了。
這題就是問了兩個問題,合并得到的最大的數和最小的數,因此建立兩個dp數組就解決了。
值得注意的是建立的這兩個dp數組的初始賦值并不一樣:
dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+sum[j]-sum[i-1]);是求最小值的,因此dp1要初始賦值為inf(最大值)。
dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]);求最大值,所以dp2初始賦值為0(最小值)。
但全部初始化完后不要忘記賦值dp1[i][i]=0;dp2[i][i]=0; 從i到i都沒辦法合并,當然是0了。
代碼:
#include<cstring> #include<cstdio> #include<algorithm> using namespace std; const int INF = 1000000; int sum[105],dp1[105][105],dp2[105][105]; int main() {int n;while(scanf("%d",&n)!=EOF){int tep;memset(sum,0,sizeof sum);memset(dp1,INF,sizeof dp1);memset(dp2,0,sizeof dp2);for(int i=1;i<=n;i++){scanf("%d",&tep);sum[i]=sum[i-1]+tep;dp1[i][i]=0;dp2[i][i]=0;}for(int len=2;len<=n;len++){for(int i=1;i<=n;i++){int j=i+len-1;if(j>n) break;for(int k=i;k<j;k++){dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+sum[j]-sum[i-1]);dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]);}}}printf("%d %d\n",dp1[1][n],dp2[1][n]);}return 0; }F - 數位dp
數位dp類似于一種數學游戲,它規定了一種游戲規則,然后讓你在一個范圍內找符合游戲規則的數字的個數,通常這個范圍是十分大的,因此傳統暴力的方法必然是會超時的,這時候dp便是一個很好的選擇,因為符合游戲規則的數字通常有某種遞推的性質,以前計算過的數如果再計算一遍必然會導致時間復雜度上升,因此我們選擇dp記憶其狀態遞推來進行解決問題,最后通過計數來算出答案。
對于這道題,我們不妨這樣設計狀態,dp[i][j]代表一共i位數字,最高位是j的windy數的個數
可以推出這樣的式子:f[i][j] = sum( f[i-1][k] ) |k - j| >= 2.
也就是說dp[i][j]必然合法的windy數的個數,那么如果前面再多一個|k - j| >= 2.的數 ,也就是dp[i+1][k],進行狀態轉移。
上面都是很好想到的,真正難處理的是:我們上述中dp[i][j]的意思一共i位數字,最高位是j的windy數的個數,然而這個dp[i][j]統計的數字的范圍是什么呢? 舉個例子dp[3][5],它統計的數字范圍是500-599的數字,可是題目若是讓你求500-550中windy數的個數該怎么辦呢?
這時候我們可以首先前綴和處理,也就是說比如讓我們求100-180的windy數,我們先求0-100和0-180的windy數的個數,在將上述兩個相減。
然后就是如何對于一個上限不確定的進行技術處理,舉個例子進行說明:
比如95387 ,dp[1.2.3.4][1.2.3…9]這些都是一定符合的,因為其位數小于五位數。再來計算五位數的,dp[5][1.2.3.4.5.6.7.8]這些也都是可以的,這些數的范圍是10000-89999。最后如何處理90000-95387這部分呢?
處理方法如下:
for(i=len-1;i;i--){for(j=0;j<w[i];j++){if(abs(w[i+1]-j)>=2){ans+=dp[i][j];}}if(abs(w[i+1]-w[i])<2) break;}.
.
.
總結
以上是生活随笔為你收集整理的AHU算法课-DP动态规划的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 持续交付2.0(一至三章)
- 下一篇: Unity3D 飞碟游戏改进版