【C++】递推&动态规划基础入门

斐波那契

递归式斐波那契函数

long long fib(long long k){
	if(k==1||k==2) return 1;
	return fib(k-1)+fib(k-2);
}

上述函数存在一定的问题,比如 f i b ( n ) = f i b ( n − 1 ) + f i b ( n − 2 ) , f i b ( n − 1 ) = f i b ( n − 2 ) + f i b ( n − 2 ) fib(n)=fib(n-1)+fib(n-2),fib(n-1)=fib(n-2)+fib(n-2) fib(n)=fib(n1)+fib(n2),fib(n1)=fib(n2)+fib(n2),其中 f i b ( n − 2 ) fib(n-2) fib(n2)被计算了两次,层层递归下去,在 n n n很大时有大量重复计算,算法效率很低,可能导致递归爆栈。

记忆化优化递归式斐波那契函数

我们只需要记录计算过的函数项,就可以提高算法效率。

long long data[101];//可记录斐波那契1~100项的值 
long long fib(long long k){
	if(data[k]!=0) return data[k];//保存过函数值直接返回 
	if(k==1||k==2) data[k]=1;
	else data[k]=fib(k-1)+fib(k-2);
	return data[k];//返回上两行计算的函数值 
}

递推式斐波那契函数

我们可以去掉递归,直接使用数组计算斐波那契函数

long long fib[101];//可记录斐波那契1~100项的值 
long long init(){
//fib[k]记录的是斐波那契第k项,使用fib数组要先调用init初始化fib数组 
	fib[1]=fib[2]=1;
	for(int i=3;i<=100;i++) fib[i]=fib[i-1]+fib[i]; 
}

基础动态规划

爬楼梯

题目描述

树老师爬楼梯,有一楼梯共 n n n级,若每次只能跨上一级或者二级,要走上 n n n级,共有多少种不同走法?
例如:楼梯一共有 3 3 3级,他可以每次都走一级,或者第一次走一级,第二次走两级也可以第一次走两级,第二次走一级,一共 3 3 3种方法。

输入格式

输入包含若干行,每行包含一个正整数 N N N,代表楼梯级数, 1 ≤ N ≤ 30 1\le N\le 30 1N30

输出格式

不同的走法数,每一行输入对应一行输出。

样例输入

5
8
10

样例输出

8
34
89

题解

每次可以走 1 1 1步或 2 2 2步,所以走第 n n n ( n ≥ 3 ) (n\ge 3) (n3)时可以看做从 n − 1 n-1 n1 1 1 1步或从 n − 2 n-2 n2 2 2 2步,走第 n n n步方案数就等于前 2 2 2步方案数之和。

#include 
using namespace std;
int main(){
    long long num[31];
    num[1]=1;
    num[2]=2;
    for(int i=3;i<=30;++i) num[i]=num[i-1]+num[i-2];
    int n;
    while(cin>>n){
        cout<<num[n]<<endl;
    }
}

骨牌铺法

题目描述

1 ∗ n 1*n 1n的一个长方形,用一个 1 ∗ 1 1*1 11 1 ∗ 2 1*2 12 1 ∗ 3 1*3 13的骨牌铺满方格。

例如当 n = 3 n=3 n=3时,共有 4 4 4种铺法。如下图:

输入格式

一个整数 n n n,表示 1 ∗ n 1*n 1n的长方形。

输出格式

一个整数表示方法总数。

样例输入

3

样例输出

4

数据范围与提示

1 < n < = 40 11<n<=40

题解

每次可以铺 1 1 1格、 2 2 2格或 3 3 3格,所以铺 n n n ( n ≥ 4 ) (n\ge 4) (n4)时可以看做从 n − 1 n-1 n1格、 n − 2 n-2 n2格或 n − 3 n-3 n3格开始铺,铺 n n n格方案数就等于 n − 1 n-1 n1格、 n − 2 n-2 n2格、 n − 3 n-3 n3格方案数之和。

#include 
using namespace std;
int main(){
	long long num[41];
	num[1]=1;
	num[2]=2;
	num[3]=4;
	for(int i=4;i<=40;++i) num[i]=num[i-1]+num[i-2]+num[i-3];
	int n;
	while(cin>>n){
		cout<<num[n]<<endl;
	}
}

爬台阶

题目描述

H H H老师爬台阶,他可以每步上 1 1 1个或 2 2 2个台阶,输入台阶的级数 ,求不同的走法数。

例如: n = 3 n=3 n=3,台阶有 3 3 3个台阶,他可以每步爬 1 1 1个台阶,或者第 1 1 1步爬 1 1 1个台级,第 2 2 2步爬 2 2 2个台阶,也可以第 1 1 1步爬 2 2 2个台阶,第 2 2 2步爬 1 1 1个台阶,一共 3 3 3种爬法。

但不幸的是,台阶上有 k k k个台阶烂了, H H H老师不能踩在这些台阶上,现在给出台阶的级数 n n n和烂的 k k k个台阶,请你计算他上台阶的方法总数。

输入格式

1 1 1行是两个 n , k n,k n,k,代表台阶数和烂台阶的数目。

2 2 2行是 k k k 1 ∼ n 1\sim n 1n的整数,表示烂台阶。

输出格式

不同的走法数。

样例输入

5 1
4

样例输出

3

数据范围与提示

100 % 的 数 据 满 足 : 1 ≤ n ≤ 60 , 0 ≤ k < n 100\%的数据满足:1\le n \le 60, 0\le k100%1n60,0k<n

题解

每次可以走 1 1 1步、 2 2 2步或 3 3 3步,所以走 n n n ( n ≥ 4 ) (n\ge 4) (n4)时可以看做从 n − 1 n-1 n1步、 n − 2 n-2 n2步或 n − 3 n-3 n3步开始走,走 n n n步方案数就等于 n − 1 n-1 n1步、 n − 2 n-2 n2步、 n − 3 n-3 n3步方案数之和,如果是烂台阶,则第 n n n步方案数位 0 0 0

#include 
#include 
using namespace std;
int main(){
	int n,k,p;
	long long num[101];
	num[0]=1;
	cin>>n>>k;
	for(int i=0;i<k;i++){
		cin>>p;
		num[p]=-1;//标记p为烂台阶
	}
	for(int i=1;i<=n;i++){
		if(i==1){
			if(num[i]==-1) num[i]=0;//烂台阶
			else num[1]=1;
		}else{
			if(num[i]==-1) num[i]=0;//烂台阶
			else num[i]=num[i-1]+num[i-2];
		}
	}
	if(num[n]==-1) printf("%lld\n",num[n-1]);
	else printf("%lld\n",num[n]);
    return 0;
}

二维动态规划

矩阵行走

题目描述

给定一个 n ∗ m n*m nm的矩阵,问从左上角的交叉点走到右下角的交叉点有多少条不同的路径(同一路径不允许重复走,也不允许往回走)。

输入格式

一行两个正整数 n , m n,m n,m

输出格式

路径数目 t t t

样例输入

6 4

样例输出

210

数据范围与提示

1 ≤ n ≤ 10 1 ≤ m ≤ 4 1\le n \le 10\\ 1\le m\le 4 1n101m4

题解

走到 ( i , j ) (i,j) (i,j)时,实际上是从 ( i − 1 , j ) (i-1,j) (i1,j) ( i , j − 1 ) (i,j-1) (i,j1)走过来的,所以路线数是从左或上走过来的路线数相加

#include 
using namespace std;
int main(){
	long long num[11][5];//记录走到(i,j)时路线数
	int n,m;
	cin>>n>>m;
	//走第一行第一列都是1条路线
	for(int j=0;j<=m;j++) num[0][j]=1;
	for(int i=0;i<=n;i++) num[i][0]=1;
	
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
		num[i][j]=num[i-1][j]+num[i][j-1];//可以从左或上走,方案数是左和上之和
	}
	cout<<num[n][m];//输出走到终点的路线数
}

走格子

题目描述

一个 N × N N×N N×N的网格,你一开始在 ( 1 , 1 ) (1,1) (1,1),即左上角。每次只能移动到下方相邻的格子或者右方相邻的格子,问到达 ( N , N ) (N,N) (N,N),即右下角有多少种方法。

但是这个问题太简单了,所以现在有 M M M个格子上有障碍,即不能走到这 M M M个格子上。

输入格式

输入文件第 1 1 1行包含两个非负整数 N , M N, M N,M,表示了网格的边长与障碍数。

接下来 M M M行,每行两个不大于 N N N的正整数 x , y x,y x,y。表示坐标 ( x , y ) (x,y) (x,y)上有障碍不能通过,且有 1 ≤ x , y ≤ n 1≤x,y≤n 1x,yn,且 x , y x, y x,y至少有一个大于 1 1 1,并请注意障碍坐标有可能相同。

输出格式

一个非负整数,为答案 m o d 100003 mod 100003 mod100003后的结果。

输入样例

3 1
3 1

输出样例

5

数据范围与提示

对于 100 100% 100的数据,有 N ≤ 1000 , M ≤ 100000 N≤1000,M≤100000 N1000,M100000

题解

解法类似于上题矩阵行走,区别就是如果 ( i , j ) (i,j) (i,j)是障碍物,则走到 ( i , j ) (i,j) (i,j)的路线数为 0 0 0

#include
using namespace std;
const int MAXN=1005;//数组大小
const int MOD=100003;//模
int n,m;
int a[MAXN][MAXN];//递推数组
bool flg[MAXN][MAXN];//标记数组
int main(){
	cin>>n>>m;//输入
	while(m--){
		int x,y;
		cin>>x>>y;
		flg[x][y]=1;//标记
	}
	a[1][1]=1;//初始值为1
	for(int i=1;i<=n;i++){//两层循环枚举方格
		for(int j=1;j<=n;j++){//同上
			a[i][j]+=a[i-1][j]+a[i][j-1];//递推式
			if(flg[i][j]==1) a[i][j]=0;//判断
            a[i][j]=a[i][j]%MOD;//模100003
		}
	}
	cout<<a[n][n];//输出路径总数
	return 0;
}

杨辉三角形

题目描述

杨辉三角形在组合数学中占有重要地位,与组合数、二项式定理等重要内容有关,如下图所示就是一个杨辉三角形:
【C++】递推&动态规划基础入门_第1张图片

通常用一个二维数组 C [ i ] [ j ] C[i][j] C[i][j]按右边示意图来存储杨辉三角形。 C [ i ] [ j ] C[i][j] C[i][j]表示第 i i i行第 j j j列的数字。
注意:行号从 0 0 0开始编号,列号也从 0 0 0开始编号。

输入格式

一行两个整数 i , j i,j i,j

输出格式

输出 C [ i ] [ j ] C[i][j] C[i][j]的值,即杨辉三角形的第 i i i行第 j j j列的元素。

样例输入

5 3

样例输出

10

数据范围与提示

0 ≤ i ≤ 60 , 0 ≤ j ≤ i 0\le i\le 60, 0\le j\le i 0i60,0ji

题解

实际上杨辉三角形可以看作是一个下三角形矩阵,每行第一个和最后一个元素是1,中间的元素是上方元素与左上元素之和。

#include 
#include 
using namespace std;
const int N=62;
const int MOD=100003;
int main(){
	long long num[N][N];
	num[0][0]=num[1][0]=num[1][1]=1;
	int n,m;
	cin>>n>>m;
	for(int i=2;i<=n;i++){
		num[i][0]=1;
		for(int j=1;j<=i-1;j++){
			num[i][j]=num[i-1][j]+num[i-1][j-1];
		}
		num[i][i]=1;
	}
	cout<<num[n][m];
}

动态规划-数字三角形模型

数字三角形

题目描述

有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数。

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5

从第一行的数开始,每次可以往左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来。如何走才能使得这个和尽量大?

输入格式

第一行输入整数 n n n表示三角形的层数。

在接下来的 n n n行中,每一行表示三角形的中每一行整数,整数之间以空格隔开。

输出格式

输出三角形从第一行的数到最后一行数所经过的数字之和的最大值。

样例输入

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

样例输出

30

数据范围与提示

1 ≤ n ≤ 500 − 10000 ≤ 三 角 形 中 的 整 数 ≤ 10000 1\le n\le 500\\ -10000\le 三角形中的整数\le 10000 1n5001000010000

题解

两种动态规划设计方式,设 n u m [ i ] [ j ] num[i][j] num[i][j]表示 ( i , j ) (i,j) (i,j)项的值

  1. d p [ i ] [ j ] dp[i][j] dp[i][j]表示从 ( 1 , 1 ) (1,1) (1,1)走到 ( i , j ) (i,j) (i,j)的最大值, d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − 1 ] , d p [ i − 1 ] [ j ] ) + n u m [ i ] [ j ] dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+num[i][j] dp[i][j]=max(dp[i1][j1],dp[i1][j])+num[i][j]
#include 
#include 
using namespace std;
#define N 1001
int dp[N][N];
int main(){
	int n;
	int ret;
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			cin>>dp[i][j];
            if(j==1) dp[i][j]+=dp[i-1][j];
            else if(j==i) dp[i][j]+=dp[i-1][j-1];
			else dp[i][j]+=max(dp[i-1][j-1],dp[i-1][j]);
			if(i==n){
				if(j==1) ret=dp[i][j];
				else ret=max(ret,dp[i][j]);
			}
		}
	}
	cout<<ret;
}
  1. d p [ i ] [ j ] dp[i][j] dp[i][j]表示从最后一行走到 ( i , j ) (i,j) (i,j)的最大值, d p [ i ] [ j ] = m a x ( d p [ i + 1 ] [ j ] , d p [ i + 1 ] [ j + 1 ] ) + n u m [ i ] [ j ] dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+num[i][j] dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+num[i][j]
#include 
#include 
using namespace std;
#define N 1001
int dp[N][N];
int main(){
	int n;
	int ret;
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			cin>>dp[i][j];
		}
	}
	for(int i=n-1;i>=1;i--){
		for(int j=1;j<=i;j++){
			dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);
		}
	}
	cout<<dp[1][1];
}

动态规划-最长上升子序列模型

最长上升子序列

题目描述

设有由 n ( 1 ≤ n ≤ 1000 ) n(1\le n\le 1000) n(1n1000)个不相同的整数组成的数列,

记为 : b ( 1 ) 、 b ( 2 ) 、 … 、 b ( n ) b(1)、b(2)、\dots、b(n) b(1)b(2)b(n)若存在 i 1 < i 2 < ⋯ < i e i_1i1<i2<<ie且有 b ( i 1 ) ≤ b ( i 2 ) ≤ ⋯ ≤ b ( i e ) b(i_1)\le b(i_2)\le \dots \le b(i_e) b(i1)b(i2)b(ie)则称为长度为 e e e的不下降序列。

程序要求,当原数列出之后,求出最长的不下降序列。

例如 13 , 7 , 9 , 16 , 38 , 24 , 37 , 18 , 44 , 19 , 21 , 22 , 63 , 15 13,7,9,16,38,24,37,18,44,19,21,22,63,15 13791638243718441921226315

例中 13 , 16 , 18 , 19 , 21 , 22 , 63 13,16,18,19,21,22,63 13161819212263就是一个长度为 7 7 7的不下降序列,同时也有 7 , 9 , 16 , 18 , 19 , 21 , 22 , 63 7 ,9,16,18,19,21,22,63 79161819212263组成的长度为 8 8 8的不下降序列。

输入格式

第一行包含整数 N N N

第二行包含 N N N个整数,表示完整序列。

输出格式

输出一个整数,表示最大长度。

输入样例

14
13 7 9 16 38 24 37 18 44 19 21 22 63 15

输出样例

8

数据范围与提示

1 ≤ N ≤ 1000 − 1 0 9 ≤ 数 列 中 的 数 ≤ 1 0 9 1\le N \le 1000\\ -10^9 \le 数列中的数 \le 10^9 1N1000109109

题解

d p [ i ] dp[i] dp[i]表示以下标 i i i结尾时最长上升长度,则 d p [ i ] = m a x ( d p [ j ] ) + 1 ( j < i , b [ j ] ≤ b [ i ] ) dp[i]=max(dp[j])+1(jdp[i]=max(dp[j])+1(j<i,b[j]b[i])

#include 
#include 
#include 
using namespace std;
#define N 1001
int dp[N];
int num[N];
int main() {
    int n,mLen = 0;
    cin>>n;
    for(int i=0;i<n;i++) {
        cin>>num[i];
    }
    for(int i=0;i<n;i++) {
        dp[i]=1;
        for(int j=0;j<i;j++) {
            if(num[j]<=num[i]){
                dp[i]=max(dp[i],dp[j]+1);
            }
        }
        mLen=max(mLen,dp[i]);
    }
    cout<<mLen;
}

最长公共子序列

题目描述

给定两个长度分别为 N N N M M M的字符串 A A A B B B,求既是 A A A的子序列又是 B B B的子序列的字符串长度最长是多少。

输入格式

第一行包含两个整数 N N N M M M

第二行包含一个长度为 N N N的字符串,表示字符串 A A A

第三行包含一个长度为 M M M的字符串,表示字符串 B B B

字符串均由小写字母构成。

输出格式

输出一个整数,表示最大长度。

输入样例

4 5
acbd
abedc

输出样例

3

数据范围与提示
1 ≤ N ≤ 1000 1\le N \le 1000 1N1000

题解

d p [ i ] [ j ] dp[i][j] dp[i][j]表示 A , B A,B A,B分别取前 i , j i,j i,j长度时最长公共子序列长度。
显然 A [ i ] = = B [ j ] A[i]==B[j] A[i]==B[j] d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j]=dp[i-1][j-1]+1 dp[i][j]=dp[i1][j1]+1 A [ i ] ≠ B [ j ] A[i]\ne B[j] A[i]=B[j] d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-1]) dp[i][j]=max(dp[i1][j],dp[i][j1])

#include 
#include 
using namespace std;
#define N 1001
int dp[N][N];
char s1[N],s2[N];
int main()
{
	int n1,n2;
	scanf("%d%d%s%s",&n1,&n2,s1+1,s2+1);
	//+1是因为保留下标0
	for(int i=0;i<=n1;i++){
		for(int j=0;j<=n2;j++){
			if(i==0||j==0){
				dp[i][j]=0;//有下标0是空串
				continue;
			}
			if(s1[i]==s2[j]) dp[i][j]=dp[i-1][j-1]+1;
			else{
				dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
			}
		}
	}
	printf("%d",dp[n1][n2]);
	return 0;
}

动态规划-背包问题

01背包

题目描述

N N N件物品和一个容量是 V V V的背包。每件物品只能使用一次。

i i i件物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式
第一行两个整数, N , V N,V N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N N N行,每行两个整数 v i , w i v_i,w_i vi,wi,用空格隔开,分别表示第 i i i件物品的体积和价值。

题解
d p [ i ] [ j ] dp[i][j] dp[i][j]表示取前 i i i个物品装入最大容量为 j j j的背包所获得最大价值。

  1. j − v [ i ] > = 0 j-v[i]>=0 jv[i]>=0表示能装下物品 i i i d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]) dp[i][j]=max(dp[i1][j],dp[i1][jv[i]]+w[i]) m a x max max的两项分别表示装货不装物品 i i i
  2. j − v [ i ] < 0 j-v[i]<0 jv[i]<0表示不能装下物品 i i i d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j]
#include 
#define maxL 1001
using namespace std;
int main(){
	int dp[maxL][maxL],v[maxL],w[maxL];
	//dp[i][j]表示选取i号背包使用j空间的最大价值 
	int n,maxW;
	cin>>n>>maxW;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	for(int i=0;i<=n;i++){
		for(int j=0;j<=maxW;j++){
			if(i==0||j==0) dp[i][j]=0; 
			else if(j<v[i]) dp[i][j]=dp[i-1][j];
			else dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
		}
	}
	cout<<dp[n][maxW];
	return 0;
}

完全背包

题目描述

N N N种物品和一个容量是 V V V的背包,每种物品都有无限件可用。

i i i种物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数 N , V N,V N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N N N行,每行两个整数 v i , w i v_i,w_i vi,wi,用空格隔开,分别表示第 i i i种物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例

10 

数据范围与提示
0 < N , V ≤ 10000 < v i , w i ≤ 1000 00<N,V10000<vi,wi1000

题解
以01背包问题为基础,用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示取前 i i i个物品装入最大容量为 j j j的背包所获得最大价值。
j − k ∗ v [ i ] > = 0 j-k*v[i]>=0 jkv[i]>=0表示能装下 k k k个物品 i i i d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − k ∗ v [ i ] ] + k ∗ w [ i ] ) dp[i][j]=max(dp[i-1][j-k*v[i]]+k*w[i]) dp[i][j]=max(dp[i1][jkv[i]]+kw[i])

#include 
using namespace std;
#define N 10001
int dp[N][N];
int v[N],w[N];
int n,V;
int main(){
	cin>>n>>V;
	for(int i=1;i<=n;i++){
		cin>>v[i]>>w[i];//输入
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=V;j++){
			for(int k=0;k*v[i]<=j;k++){
				dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
				//求如何装入可得到最大的dp[i][j]
			}
		}
	}
	cout<<dp[n][V];
}

版权声明

  • 本文档归cout0所有,仅供学习使用,未经允许,不得转载。

你可能感兴趣的:(C/C++,c++,动态规划,算法)