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

159 阅读3分钟

斐波那契

递归式斐波那契函数

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

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

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

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

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

基础动态规划

爬楼梯

题目描述

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

输入格式

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

输出格式

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

样例输入

5
8
10

样例输出

8
34
89

题解

每次可以走11步或22步,所以走第nn(n3)(n\ge 3)时可以看做从n1n-111步或从n2n-222步,走第nn步方案数就等于前22步方案数之和。

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

骨牌铺法

题目描述

1n1*n的一个长方形,用一个 111*1121*2131*3的骨牌铺满方格。

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

输入格式

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

输出格式

一个整数表示方法总数。

样例输入

3

样例输出

4

数据范围与提示

1<n<=401<n<=40

题解

每次可以铺11格、22格或33格,所以铺nn(n4)(n\ge 4)时可以看做从n1n-1格、n2n-2格或n3n-3格开始铺,铺nn格方案数就等于n1n-1格、n2n-2格、n3n-3格方案数之和。

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

爬台阶

题目描述

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

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

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

输入格式

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

22行是kk1n1\sim n的整数,表示烂台阶。

输出格式

不同的走法数。

样例输入

5 1
4

样例输出

3

数据范围与提示

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

题解

每次可以走11步、22步或33步,所以走nn(n4)(n\ge 4)时可以看做从n1n-1步、n2n-2步或n3n-3步开始走,走nn步方案数就等于n1n-1步、n2n-2步、n3n-3步方案数之和,如果是烂台阶,则第nn步方案数位00

#include <iostream>
#include <algorithm>
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;
}

二维动态规划

矩阵行走

题目描述

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

输入格式

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

输出格式

路径数目tt

样例输入

6 4

样例输出

210

数据范围与提示

1n101m41\le n \le 10\\ 1\le m\le 4

题解

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

#include <iostream>
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×NN×N的网格,你一开始在(1,1)(1,1),即左上角。每次只能移动到下方相邻的格子或者右方相邻的格子,问到达(N,N)(N,N),即右下角有多少种方法。

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

输入格式

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

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

输出格式

一个非负整数,为答案mod100003mod 100003后的结果。

输入样例

3 1
3 1

输出样例

5

数据范围与提示

对于100100%的数据,有N1000,M100000N≤1000,M≤100000

题解

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

#include<iostream>
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[i][j]C[i][j]按右边示意图来存储杨辉三角形。C[i][j]C[i][j]表示第ii行第jj列的数字。 注意:行号从00开始编号,列号也从00开始编号。

输入格式

一行两个整数i,ji,j

输出格式

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

样例输入

5 3

样例输出

10

数据范围与提示

0i60,0ji0\le i\le 60, 0\le j\le i

题解

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

#include <iostream>
#include <algorithm>
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

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

输入格式

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

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

输出格式

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

样例输入

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

样例输出

30

数据范围与提示

1n50010000三角形中的整数100001\le n\le 500\\ -10000\le 三角形中的整数\le 10000

题解

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

  1. dp[i][j]dp[i][j]表示从(1,1)(1,1)走到(i,j)(i,j)的最大值,dp[i][j]=max(dp[i1][j1],dp[i1][j])+num[i][j]dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+num[i][j]
#include <iostream>
#include <algorithm>
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. dp[i][j]dp[i][j]表示从最后一行走到(i,j)(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 <iostream>
#include <algorithm>
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(1n1000)n(1\le n\le 1000)个不相同的整数组成的数列,

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

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

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

例中1316181921226313,16,18,19,21,22,63就是一个长度为77的不下降序列,同时也有791618192122637 ,9,16,18,19,21,22,63组成的长度为88的不下降序列。

输入格式

第一行包含整数NN

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

输出格式

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

输入样例

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

输出样例

8

数据范围与提示

1N1000109数列中的数1091\le N \le 1000\\ -10^9 \le 数列中的数 \le 10^9

题解

dp[i]dp[i]表示以下标ii结尾时最长上升长度,则dp[i]=max(dp[j])+1(j<i,b[j]b[i])dp[i]=max(dp[j])+1(j<i,b[j]\le b[i])

#include <iostream>
#include <string>
#include <algorithm>
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;
}

最长公共子序列

题目描述

给定两个长度分别为NNMM的字符串AABB,求既是AA的子序列又是BB的子序列的字符串长度最长是多少。

输入格式

第一行包含两个整数NNMM

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

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

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

输出格式

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

输入样例

4 5
acbd
abedc

输出样例

3

数据范围与提示

1N10001\le N \le 1000

题解

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

#include <iostream>
#include <string>
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背包

题目描述

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

ii件物品的体积是viv_i,价值是wiw_i

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

输出最大价值。

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

接下来有NN行,每行两个整数vi,wiv_i,w_i,用空格隔开,分别表示第ii件物品的体积和价值。

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

  1. jv[i]>=0j-v[i]>=0表示能装下物品iidp[i][j]=max(dp[i1][j],dp[i1][jv[i]]+w[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])maxmax的两项分别表示装货不装物品ii
  2. jv[i]<0j-v[i]<0表示不能装下物品iidp[i][j]=dp[i1][j]dp[i][j]=dp[i-1][j]
#include <iostream>
#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;
}

完全背包

题目描述

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

ii种物品的体积是viv_i,价值是wiw_i

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

输入格式

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

接下来有NN行,每行两个整数vi,wiv_i,w_i,用空格隔开,分别表示第ii种物品的体积和价值。

输出格式

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

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例

10 

数据范围与提示

0<N,V10000<vi,wi10000<N,V\le 10000<v_i,w_i\le 1000

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

#include <iostream>
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所有,仅供学习使用,未经允许,不得转载。