前言:
博主是一名大一編程小白,因爲馬上要參加藍橋杯,所以最近一直在學習動態規劃,接下來我將分享我遇到的經典例題和我能力所及的最清晰的代碼,並且會逐漸豐富文章內容,分享思路,希望和大家共同進步!
因爲內容較多,建議收藏慢慢研究。
學習筆記:
動態規劃題目特點
1.計數
—有多少種方式走到右下角
—有多少種方法選出k個數使得和爲sum
2.求最大最小值
—從左上角走到右下角路徑的最大數字和
—最長上升子序列長度
3.求存在性
—取石子游戲,先手是否必勝
—能不能選出k個數使得和爲sum
動態規劃組成部分一:確定狀態
最後一步(最優策略的最後一步)
化成子問題
動態規劃組成部分二:轉移方程
動態規劃組成部分三:初始條件和邊界情況
用轉移方程算不出來,需要手工定義
動態規劃組成部分四:計算順序
利用之前的計算結果
一維從小到大(大部分)
二維從上到下,從左到右(大部分)常見動態規劃類型
座標型動態規劃
序列型動態規劃
劃分型動態規劃
區間型動態規劃
揹包型動態規劃
最長序列型動態規劃
博弈型動態規劃
綜合性動態規劃
例一 Unique Paths
題目描述:
給定m行n列的網格,有一個機器人從左上角(0,0)出發,每一步可以向下或者向右走一步,問有多少種不同的方式走到右下角?
代碼如下:
#include<bits/stdc++.h>
using namespace std;
void dp(int m,int n)
{
int f[m][n];
memset(f,0,sizeof(f));
int i,j;
f[0][0]=1;
for(j=0;j<n;j++)
f[0][j]=1;
for(i=0;i<m;i++)
f[i][0]=1;
for(i=1;i<m;i++)
{
for(j=1;j<n;j++)
{
f[i][j]=f[i-1][j]+f[i][j-1];
}
}
printf("%d\n",f[m-1][n-1]);
}
int main()
{
int m,n;
scanf("%d%d",&m,&n);
dp(m,n);
return 0;
}
運行結果:
例二 激光樣式
題目描述:
x星球的盛大節日爲增加氣氛,用30臺激光器一字排開,向太空中打出光柱。
安裝調試的時候才發現,不知什麼原因,相鄰的兩臺激光器不能同時打開!
國王很想知道,在目前這種bug存在的情況下,一共能打出多少種激光效果?
顯然,如果只有3臺機器,一共可以成5種樣式,即:
全都關上(sorry, 此時無聲勝有聲,這也算一種)
開一臺,共3種
開兩臺,只1種
30臺就不好算了,國王只好請你幫忙了。
要求提交一個整數,表示30臺激光器能形成的樣式種數。
注意,只提交一個整數,不要填寫任何多餘的內容。
網上摘抄代碼如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
bool get(int x){
if(x&(x<<1))return false;
else return true;
}
int main(int argc, char *argv[]) {
int ans=0;
for(int i=0;i<1<<30;i++){
if(get(i)){
ans++;
}
}
cout<<ans<<endl;
return 0;
}
網上摘抄代碼運行結果:
我的思路:
我的代碼如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int arr[31];
int i;
arr[1]=2;
arr[2]=3;
for(i=3;i<=30;i++)
arr[i]=arr[i-1]+arr[i-2];
printf("%d\n",arr[30]);
}
我的代碼運行結果:
疑問:
我直接找到了轉移方程和出口,此代碼非常簡單,有點小學生找規律的味道,不知道如果在藍橋杯這樣寫對不對。請大佬指正,感謝!
例三 Unique Paths II
題目描述:
給定m行n列的網格,有一個機器人從左上角(0,0)出發,每一次可以向下或者向右走一步,網格中有些地方有障礙,機器人不能通過障礙。問:有多少種不同的方式走到右下角?
代碼如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int m,n;
scanf("%d %d",&m,&n); //輸入行列數m,n
int f[m][n],dp[m][n];
int i,j,k;
for(i=0;i<m;i++) //輸入m*n網格,1爲有障礙,0爲無障礙
{
for(j=0;j<n;j++)
{
scanf("%d",&f[i][j]);
}
}
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
{
if(f[i][j]==1)
dp[i][j]=0;
else
{
if(i==0&&j==0)
dp[i][j]=1;
else
{
dp[i][j]=0;
if(i-1>=0)
dp[i][j]+=dp[i-1][j];
if(j-1>=0)
dp[i][j]+=dp[i][j-1];
}
}
}
}
printf("%d\n",dp[m-1][n-1]);
return 0;
}
運行結果:
疑惑:
好像運行時間有點長???
例四 Jump Game
題目描述:
有n塊石頭分別在x軸的0,1,2,…,n-1位置上。一隻青蛙在石頭0,想跳到石頭n-1上。如果一隻青蛙在第i塊石頭上,它最多可以向右跳ai。問,青蛙能否跳到n-1?
代碼如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int i,j,n;
scanf("%d",&n); //輸入石頭的數量
int a[n],f[n];
for(i=0;i<n;i++) //依次輸入在第i塊石頭上,最多可以向右跳的距離
scanf("%d",&a[i]);
f[0]=1;
for(j=1;j<n;j++)
{
f[j]=0;
for(i=0;i<j;i++)
{
if(f[i]==1&&i+a[i]>=j)
{
f[j]=1;
break;
}
}
}
if(f[n-1]==1)
printf("Yes\n");
else
printf("No\n");
}
運行結果:
例五 Paint House
題目描述:
有一排N棟房子,每棟房子要漆成3種顏色中的一種:紅、藍、綠。任何兩棟相鄰的房子不能漆成同樣的顏色第i棟房子染成紅色、藍色、綠色的花費分別是cost[i][0]、cost[i][2]、cost[i][2]。問:最少花多少錢漆這個房子?
代碼如下:
#include<bits/stdc++.h>
using namespace std;
int MIN(int m,int n,int t)
{
if(m>n)
m=n;
return m>t?t:m;
}
int main()
{
int N,i,j;
scanf("%d",&N); //輸入房子數量
int cost[N][3],f[N+1][3]; //i表示前i棟 i表示前i棟 i表示前i棟 i表示前i棟
for(i=0;i<N;i++) //分別輸入每棟房子染成紅色、藍色、綠色的花費
for(j=0;j<3;j++)
{
scanf("%d",&cost[i][j]);
}
f[0][0]=f[0][1]=f[0][2]=0;
for(i=1;i<=N;i++)
{
f[i][0]=min(f[i-1][1]+cost[i-1][0],f[i-1][2]+cost[i-1][0]);
f[i][1]=min(f[i-1][0]+cost[i-1][1],f[i-1][2]+cost[i-1][1]);
f[i][2]=min(f[i-1][0]+cost[i-1][2],f[i-1][1]+cost[i-1][2]);
}
printf("%d\n",MIN(f[N][0],f[N][1],f[N][2]));
return 0;
}
運行結果:
疑問:
運行速度一如既往得慢,希望大佬提出改進意見,感謝!
例六 Minimum Path Sum
題目描述:
給定m行n列的網格,每個格子(i,j)裏都有一個非負數A[i][j],求一個從左上角(0,0)到右下角的路徑,每一步只能向下或者向右走一步,使得路徑上的格子裏的數字之和最小,輸出最小數字和。
代碼如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int i,j,m,n;
scanf("%d %d",&m,&n);
int A[m][n],f[m][n];
for(i=0;i<m;i++)
for(j=0;j<n;j++)
scanf("%d",&A[i][j]);
f[0][0]=A[0][0];
for(i=1;i<m;i++)
f[i][0]=f[i-1][0]+A[i][0];
for(j=1;j<n;j++)
f[0][j]=f[0][j-1]+A[0][j];
for(i=1;i<m;i++)
for(j=1;j<n;j++)
f[i][j]=min(f[i-1][j],f[i][j-1])+A[i][j];
printf("%d\n",f[m-1][n-1]);
return 0;
}
運行結果:
優化代碼:
滾動數組滾動數組滾動數組
#include<bits/stdc++.h>
using namespace std;
int main()
{
int i,j,m,n;
scanf("%d %d",&m,&n);
int A[m][n],dp[2][n];
for(i=0;i<m;i++)
for(j=0;j<n;j++)
scanf("%d",&A[i][j]);
for(i=0;i<m;i++)
{
if(i==0)
{
dp[0][0]=A[0][0];
for(j=1;j<n;j++)
{
dp[0][j]=dp[0][j-1]+A[0][j];
}
}
else
{
if(i%2==1)
{
dp[1][0]=dp[0][0];
for(j=1;j<n;j++)
{
dp[1][j]=min(dp[0][j],dp[1][j-1])+A[i][j];
}
}
else
{
dp[0][0]=dp[1][0];
for(j=1;j<n;j++)
{
dp[0][j]=min(dp[1][j],dp[0][j-1])+A[i][j];
}
}
}
}
if(i%2==1)
{
printf("%d\n",dp[1][n-1]);
}
else
{
printf("%d\n",dp[0][n-1]);
}
return 0;
}
優化代碼運行結果:
例七 數字三角形問題
題目描述:
給定一個由n行數字組成的數字三角形如下圖所示。試設計一個算法,計算出從三角形的頂至底的一條路徑,使該路徑經過的數字總和最大。
對於給定的由n行數字組成的數字三角形,計算從三角形的頂至底的路徑經過的數字和的最大值。
代碼如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,i,j;
scanf("%d",&n);
int a[n][n];
memset(a,0,sizeof(a));
for(i=0;i<n;i++)
for(j=0;j<=i;j++)
scanf("%d",&a[i][j]);
int dp[n][n];
memset(dp,0,sizeof(dp));
for(j=0;j<n;j++)
dp[n-1][j]=a[n-1][j];
for(i=n-2;i>=0;i--)
{
for(j=0;j<=i;j++)
{
dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
}
}
printf("%d\n",dp[0][0]);
return 0;
}
#include<iostream>
#include<algorithm>
#define MAX 101
using namespace std;
int n;
int D[MAX][MAX];
int maxsum[MAX][MAX];
int MaxSum(int i,int j)
{
if(maxsum[i][j]!=-1)
return maxsum[i][j];
if(i==n)
maxsum[i][j]=D[i][j];
else
{
int x=MaxSum(i+1,j);
int y=MaxSum(i+1,j+1);
maxsum[i][j]=max(x,y)+D[i][j];
}
return maxsum[i][j];
}
int main()
{
cin>>n;
int i,j;
for(i=1;i<=n;++i)
for(j=1;j<=i;++j)
{
cin>>D[i][j];
maxsum[i][j]=-1;
}
cout<<MaxSum(1,1)<<endl;
return 0;
}
另附遞歸代碼:
#include<bits/stdc++.h>
#define MAX 101
using namespace std;
int n;
int a[MAX][MAX];
int maxsum[MAX][MAX]; //記錄避免重複運算
int Maxsum(int i,int j)
{
if(maxsum[i][j]!=-1)
return maxsum[i][j];
if(i==n)
maxsum[i][j]=a[i][j];
else
maxsum[i][j]=max(Maxsum(i+1,j),Maxsum(i+1,j+1))+a[i][j];
return maxsum[i][j];
}
int main()
{
int i,j;
scanf("%d",&n); //三角形行數
for(i=1;i<=n;++i) //從第一行開始輸入
for(j=1;j<=i;++j)
{
scanf("%d",&a[i][j]);
maxsum[i][j]=-1;
}
printf("%d\n",Maxsum(1,1));
return 0;
}
空間優化代碼:
#include<iostream>
#include<algorithm>
#define MAX 101
using namespace std;
int n;
int *maxsum;
int D[MAX][MAX];
int main()
{
cin>>n;
int i,j;
for(i=1;i<=n;++i)
for(j=1;j<=i;++j)
cin>>D[i][j];
maxsum=D[n];
for(i=n-1;i>=1;--i)
for(j=1;j<=i;++j)
maxsum[j]=max(maxsum[j],maxsum[j+1])+D[i][j];
cout<<maxsum[1]<<endl;
return 0;
}
運行結果:
例八 K好數
題目描述:
如果一個自然數N的K進製表示中任意的相鄰的兩位都不是相鄰的數字,那麼我們就說這個數是K好數。求L位K進制數中K好數的數目。
例如K = 4,L = 2的時候,所有K好數爲11、13、20、22、30、31、33 共7個。由於這個數目很大,請你輸出它對1000000007取模後的值。
代碼如下:
#include<bits/stdc++.h>
using namespace std;
#define NUM 1000000007;
int main()
{
int K,L;
int i,j,x,count=0;
scanf("%d%d",&K,&L);
int arr[L+1][K];
memset(arr,0,sizeof(arr));
for(i=0;i<K;++i)
arr[1][i]=1;
for(i=2;i<=L;++i)
{
for(j=0;j<K;++j)
{
for(x=0;x<K;++x)
{
if(x!=j-1&&x!=j+1)
{
arr[i][j]+=arr[i-1][x];
arr[i][j]%=NUM;
}
}
}
}
for(i=1;i<K;++i)
{
count+=arr[L][i];
count%=NUM;
}
printf("%d\n",count);
return 0;
}
運行結果:
例九 最長上升子序列
例十 最長公共子序列
題目描述:
給出兩個字符串,求出這樣一個最長的公共子序列的長度:子序列中的每個字符都能在兩個原字符串中找到,而且每個字符的先後順序和原字符串中的先後順序一致。
圖解:
代碼如下:
#include<iostream>
#include<cstring>
using namespace std;
char str1[1000];
char str2[1000];
int maxlength[1000][1000];
int main()
{
cin>>str1>>str2;
int length1=strlen(str1);
int length2=strlen(str2);
int i,j;
//maxlength[i][j]表示str1左邊i個字符形成的子字符串和str2左邊的j個字符形成的子字符串的最長公共子序列的長度
for(i=0;i<=length1;++i)
maxlength[i][0]=0;
for(j=0;j<=length2;++j)
maxlength[0][j]=0;
for(i=1;i<=length1;++i)
{
for(j=1;j<=length2;++j)
{
if(str1[i-1]==str2[j-1])
maxlength[i][j]=maxlength[i-1][j-1]+1;
else
maxlength[i][j]=max(maxlength[i-1][j],maxlength[i][j-1]);
}
}
cout<<maxlength[length1][length2]<<endl;
return 0;
}
運行結果:
例十一 最佳加法表達式
例十二 神奇的口袋
題目描述:
有一個神奇的口袋,總的容積是40,用這個口袋可以變出一些物品,這些物品的總體積必須是40。John現在有n個想要得到的物品,每個物品的體積分別是 a1,a2……an。John可以從這些物品中選擇一些,如果選出的物體的總體積是40,那麼利用這個神奇的口袋,John就可以得到這些物品。現在的問題是,John有多少種不同的選擇物品的方式。
輸入:
輸入的第一行是正整數n (1 <= n <= 20),表示不同的物品的數目。接下來的n行,每行有一個1到40之間的正整數,分別給出 a1,a2……an的值。
輸出:
輸出不同的選擇物品的方式的數目。
代碼如下:
#include<iostream>
#include<cstring>
using namespace std;
int N;
int a[20+1];
int Ways[40+1][20+1]; //Ways[i][j]表示從前j中物品中湊出體積i的方法數
int main()
{
cin>>N;
memset(Ways,0,sizeof(Ways));
for(int i=1;i<=N;++i) //下標從1開始
{
cin>>a[i];
Ways[0][i]=1;
}
Ways[0][0]=1;
for(int m=1;m<=40;++m)
{
for(int k=1;k<=N;++k)
{
Ways[m][k]=Ways[m][k-1];
if(m-a[k]>=0)
Ways[m][k]+=Ways[m-a[k]][k-1];
}
}
cout<<Ways[40][N]<<endl;
return 0;
}
/*
#include<iostream>
using namespace std;
int a[21];
int Ways(int m,int k)
{
if(m==0)
return 1;
if(k<=0)
return 0;
return Ways(m,k-1)+Ways(m-a[k],k-1);
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i];
cout<<Ways(40,n)<<endl;
return 0;
}
*/
運行結果:
例十三 0-1揹包問題
例十四 多重揹包問題
https://blog.csdn.net/weixin_45953673/article/details/104932290