一篇文章帶你快速入門DP動態規劃——C++

前言:

博主是一名大一編程小白,因爲馬上要參加藍橋杯,所以最近一直在學習動態規劃,接下來我將分享我遇到的經典例題和我能力所及的最清晰的代碼,並且會逐漸豐富文章內容,分享思路,希望和大家共同進步!

因爲內容較多,建議收藏慢慢研究。

學習筆記:

動態規劃題目特點
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;
} 

運行結果:

例九    最長上升子序列

最長上升子序列(動態規劃)——C++

例十    最長公共子序列

題目描述:

給出兩個字符串,求出這樣一個最長的公共子序列的長度:子序列中的每個字符都能在兩個原字符串中找到,而且每個字符的先後順序和原字符串中的先後順序一致。

圖解:

代碼如下:

#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;
}

運行結果:

例十一    最佳加法表達式

最佳加法表達式——動態規劃詳解——C++

例十二    神奇的口袋

題目描述:

有一個神奇的口袋,總的容積是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揹包問題  

0-1揹包問題—動態規劃+滾動數組—C++超詳解

例十四    多重揹包問題

https://blog.csdn.net/weixin_45953673/article/details/104932290

例十五    完全揹包問題

完全揹包問題——動態規劃——C++詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章