算法篇之动态规划

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Mind_programmonkey/article/details/88710516

                                         算法篇之动态规划

                                                                                                                      --细说动态规划(细说不是胡说)

目录

前言

一、什么是动态规划

二、动态规划的求解

1.台阶问题

(1)递归求解(自顶向下)

(2)备忘录算法

(3)动态规划(自底向上)

三、更多例子

1.数字三角形

2.背包问题

(1)苹果

(2)完全背包问题

(3)开心的小明

3.回文字符串与最长公共子序列 

(1)最长公共子序列内存限制:64MB 时间限

(2)单调递增最长子序列


 

前言

     最近因为复试的原因,对几个算法进行了一定程度的了解,这一篇将详细讲述一下动态规划,让大家浅显易懂的明白动态规划。

一、什么是动态规划

官方解释:动态规划的英文名Dynamic Programming,是一种分阶段求解决决策问题的数学思想。

通俗来讲:这里借助一张从别的大佬csdn博客上看到的图片来展开,动态规划其实就是利用记住已经解决过的子问题的解,闲话不多说,让我们直接看例子吧。

二、动态规划的求解

       下面将以一个问题的具体求解,来让大家具体的掌握动态规划的求解。

1.台阶问题

        有一座高度时10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。要求用程序来求出一共有多少种走法。

思路讲解:

(1)递归求解(自顶向下)

       假设这时候我们只差最后步就走到第10级台阶,那么此时我们有两种情况,一种是从第8级走一步上2级到第10级,另外一种是从第9级走一步上1级到底10级,即我们要想走到第10级,最后一步必然是从8级或者9级开始的。

       那么假设我们已经知道0到9级台阶的走法有X种,0到8级台阶的走法有Y种,那么0到10级台阶的走法应该是X+Y。即可以表示为 F(10)=F(9)+F(8)

#include<iostream>
using namespace std;
int stairs(int n)
{
	if(n==1)
		return 1;
	else if(n==2)
		return 2;
	else 
		return stairs(n-1)+stairs(n-2);
}
int main()
{
	int n;
	cin>>n;
	cout<<stairs(n)<<endl;
	return 0;	
} 

       然而,这个时间复杂度有点高,时间复杂度如下图一棵二叉树,可以近似看复杂度为O(2^N)

(2)备忘录算法

          回顾上面的地归途,我们可以看出有些相同的参数被重复计算量,越往下,重复的越多,这时候我们可以暂存一下结果。

#include<iostream>
#include<map>
using namespace std;
int stairs(int n)
{
	map<int,int> mp;
	if(n<1)
		return 0;
	else if(n==1)
		return 1;
	else if(n==2)
		return 2;
	if(mp[n]!=0)
		return mp[n];
	else
	{
		int temp=stairs(n-1)+stairs(n-2);
		mp[n]=temp;
		return temp;
	}
}
int main()
{
	int n;
	cin>>n;
	cout<<stairs(n)<<endl;
	return 0;	
} 

(3)动态规划(自底向上)

        接下来就来到了重头戏了,既然我们时间复杂度已经优化了,那么空间复杂度呢?我们从头开始迭代,在每一次迭代过程中,只要保留之前的两个状态,就可以推导出新的状态,而不需要像上述备忘录算法那样保留全部的子状态。

#include<iostream>
using namespace std;
int stairs(int n)
{
	if(n<1)
		return 0;
	if(n==1)
		return 1;
	if(n==2)
		return 2;
	int stairs1=1;
	int stairs2=2;
	int temp=0;
	for(int i=3;i<=n;i++)
	{
		temp=stairs1+stairs2;
		stairs1=stairs2;
		stairs2=temp;
	}	  
	return temp;
}
int main()
{
	int n;
	cin>>n;
	cout<<stairs(n)<<endl;
	return 0;	
} 

三、更多例子

    通过例子大家更好的来掌握动态规划,我尽量将解法差不多的题目放在了一起。

1.数字三角形

题目描述 Description

        如图所示的数字三角形,从顶部出发,在每一结点可以选择向左走或得向右走,一直走到底层,要求找出一条路径,使路径上的值最大。

输入描述 Input Description

第一行是数塔层数N(1<=N<=100)。

第二行起,按数塔图形,有一个或多个的整数,表示该层节点的值,共有N行。

输出描述 Output Description

输出最大值。

样例输入 Sample Input

5

13

11 8

12 7 26

6 14 15 8

12 7 13 24 11

样例输出 Sample Output

86

#include<iostream>
#include<algorithm>
#define maxsize 101
using namespace std;
int n;
int d[maxsize][maxsize];
int s[maxsize][maxsize];
int maxsum(int i,int j)
{
	if(s[i][j]!=-1)
		return s[i][j];
	if(i==n)
		s[i][j]=d[i][j];
	else
	{
		int x=maxsum(i+1,j);
		int y=maxsum(i+1,j+1);
		s[i][j]=max(x,y)+d[i][j];
	}
	return s[i][j];
}
int main()
{
	int i,j;
	cin>>n;
	for(i=1;i<=n;i++)
		for(j=1;j<=i;j++)
		{
			cin>>d[i][j];
			s[i][j]=-1;
		 } 
	cout<<maxsum(1,1)<<endl;
	return 0;
 } 

2.背包问题

(1)苹果

题目描述:

ctest有n个苹果,要将它放入容量为v的背包。给出第i个苹果的大小和价钱,求出能放入背包的苹果的总价钱最大值。

输入描述:

有多组测试数据,每组测试数据第一行为2个正整数,分别代表苹果的个数n和背包的容量v,n、v同时为0时结束测试,此时不输出。接下来的n行,每行2个正整数,用空格隔开,分别代表苹果的大小c和价钱w。所有输入数字的范围大于等于0,小于等于1000。

输出描述:

对每组测试数据输出一个整数,代表能放入背包的苹果的总价值。

样例输入:

3 3
1 1
2 1
3 1
0 0

样例输出:

2

第一种解法:

#include<iostream>
#include<algorithm>
#define maxsize 1001
using namespace std;
struct node{
	int c;
	int w;
}; 
node apple[maxsize];
int dp[maxsize][maxsize];

int main()
{
	int n,v;
	while(cin>>n>>v)
	{
		if(n==0&&v==0)
			break;
		for(int i=1;i<=n;i++)
			cin>>apple[i].c>>apple[i].w; 
		for(int i=1;i<=n;i++)
			for(int j=1;j<=v;j++)
			{
				if(j<apple[i].c)
					dp[i][j]=dp[i-1][j];
				else
					dp[i][j]=max(dp[i-1][j],dp[i-1][j-apple[i].c]+apple[i].w);
			}
		cout<<dp[n][v]<<endl;
	}
	return 0;
}

 第二种解法(第二种我在南阳oj上跑的话时间超时了)

#include<iostream>
#include<algorithm>
#define maxsize 1001
int dfs[maxsize];
using namespace std;
int main()
{
	int n,v;
	while(cin>>n>>v)
	{
		if(n==0&&v==0)
			break;
		while(n--)
		{
			int c,w;
			cin>>c>>w;
			for(int i=1;i<=n;i++)
			{
				for(int j=v;j>=c;j--)//从大到小来,物品不可重复
					dfs[j]=max(dfs[j],dfs[j-c]+w);
			}
		}
		cout<<dfs[v];
	}
}

(2)完全背包问题

(这两个问题有点类似,对照着进行学习,一个物品可重复,一个物品不可重复)

题目描述:

直接说题意,完全背包定义有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。本题要求是背包恰好装满背包时,求出最大价值总和是多少。如果不能恰好装满背包,输出NO

输入描述:

第一行: N 表示有多少组测试数据(N<7)。 
接下来每组测试数据的第一行有两个整数M,V。 M表示物品种类的数目,V表示背包的总容量。(0<M<=2000,0<V<=50000)
接下来的M行每行有两个整数c,w分别表示每种物品的重量和价值(0<c<100000,0<w<100000)

输出描述:

对应每组测试数据输出结果(如果能恰好装满背包,输出装满背包时背包内物品的最大价值总和。 如果不能恰好装满背包,输出NO)

样例输入:

2
1 5
2 2
2 5
2 2
5 1

样例输出:

NO
1
#include<iostream>
#include<algorithm>
#include<string.h>
#define maxsize 50050
using namespace std;
int dp[maxsize];
struct node{
	int c;
	int w;
};
node bag[maxsize];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int m,v;
		cin>>m>>v;
		for(int i=1;i<=m;i++)
			cin>>bag[i].c>>bag[i].w;
		for(int i=0;i<=v;i++)	
			dp[i]=-0x3f3f3f3f;
		dp[0]=0;
		for(int i=1;i<=m;i++)
			for(int j=bag[i].c;j<=v;j++)//从小到大来的,背包可重复 
				dp[j]=max(dp[j],dp[j-bag[i].c]+bag[i].w);
		if(dp[v]>0)
			cout<<dp[v]<<endl;
		else
			cout<<"NO"<<endl;
	 } 
	 return 0;
}

(3)开心的小明

(这道题与前两道题就类似了,大家学以致用的做一下) 

题目描述:

       小明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N 元钱就行”。今天一早小明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N 元。于是,他把每件物品规定了一个重要度,分为5 等:用整数1~5 表示,第5 等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过N 元(可以等于N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。设第j 件物品的价格为v[j],重要度为w[j],共选中了k 件物品,编号依次为j1...jk,则所求的总和为:v[j1]*w[j1]+..+v[jk]*w[jk]请你帮助金明设计一个满足要求的购物单.

 

输入描述:

      第一行输入一个整数N(0<N<=101)表示测试数据组数,每组测试数据输入的第1 行,为两个正整数,用一个空格隔开:N m(其中N(<30000)表示总钱数,m(<25)为希望购买物品的个数。)从第2 行到第m+1 行,第j 行给出了编号为j-1的物品的基本数据,每行有2 个非负整数v p(其中v 表示该物品的价格(v≤10000),p 表示该物品的重要度(1~5))

输出描述:

     每组测试数据输出只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的 最大值(<100000000)

样例输入:

1

1000 5

800 2

400 5

300 5

400 3

200 2

样例输出:

3900
#include<iostream>
#include<algorithm>
#include<string.h>
#define maxsize 30001
using namespace std;
int dp[maxsize];
struct node{
	int v;
	int p;
};
int main()
{
	int T;
	node bags[maxsize];
	cin>>T;
	while(T--)
	{
		memset(dp,0,sizeof(dp));
		int n,m;
		int i,j;
		cin>>n>>m;	
		for(i=1;i<=m;i++)
		{
			cin>>bags[i].v>>bags[i].p;
			bags[i].p*=bags[i].v;
		 } 
		 for(i=1;i<=m;i++)
		 	for(j=n;j>=bags[i].v;j--)
		 	{
		 		dp[j]=max(dp[j],dp[j-bags[i].v]+bags[i].p);				
			}		
		cout<<dp[n]<<endl;
	}	
	return 0;
} 

3.回文字符串与最长公共子序列 

(1)最长公共子序列内存限制:64MB 时间限

咱们就不拐弯抹角了,如题,需要你做的就是写一个程序,得出最长公共子序列。
tip:最长公共子序列也称作最长公共子串(不要求连续),英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。

输入描述:

第一行给出一个整数N(0<N<100)表示待测数据组数

接下来每组数据两行,分别为待测的两组字符串。每个字符串长度不大于1000.

输出描述:

每组测试数据输出一个整数,表示最长公共子序列长度。每组结果占一行。

样例输入:

2

asdf

adfsd

123abc

abc123abc

样例输出:

3

6

#include<iostream>
#include<algorithm>
#define maxsize 10001
using namespace std;
int a[maxsize];
int dp[maxsize];
int main()
{
	int i,j;
	int T;
	int max=0;
	cin>>T;
	while(T--)
	{
		int n;
		cin>>n;
		for(i=1;i<=n;i++)	
			cin>>a[i];
		for(i=1;i<=n;i++)
		{
			dp[i]=1;
			for(j=1;j<i;j++)
				if(a[i]<a[j]&&dp[i]<dp[j]+1)
					dp[i]=dp[j]+1;
		}
		max=0;
		for(i=1;i<=n;i++)
			if(max<dp[i])
				max=dp[i];
		cout<<max<<endl;
	}
	return 0;
}

(2)单调递增最长子序列

题目描述:

求一个字符串的最长递增子序列的长度
如:dabdbf最长递增子序列就是abdf,长度为4

输入描述:

第一行一个整数0<n<20,表示有n个字符串要处理

随后的n行,每行有一个字符串,该字符串的长度不会超过10000

输出描述:

输出字符串的最长递增子序列的长度

样例输入:

3

aaa

ababc

abklmncdefg

样例输出:

1

3

7

#include<iostream>
#include<string.h>
#include<algorithm>
#define maxsize 10001
using namespace std;
char s[maxsize];
int dp[maxsize];
int main()
{
	int T;
	cin>>T;
	int max=0;
	while(T--)
	{
		cin>>s;
		memset(dp,1,sizeof(dp));
		for(int i=0;i<strlen(s);i++)
		{
			dp[i]=1;
			for(int j=0;j<i;j++)
				if(s[i]>s[j]&&dp[i]<dp[j]+1)
					dp[i]=dp[j]+1;	
		}	
		max=0;
		for(int i=0;i<strlen(s);i++)
			if(max<dp[i])
				max=dp[i];
		cout<<max<<endl; 
	} 
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/Mind_programmonkey/article/details/88710516
今日推荐