动态规划
来源:互联网 发布:像素设计软件 编辑:程序博客网 时间:2024/06/15 10:15
例题一、数字三角形
在上面的数字三角形中寻找一条从顶部到底部的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或者右下走,只需要求出最大的和,不必给出具体路径
结题思路:
用二维数组存放数字三角形
D(r,j):第r行第j个数字
MaxSum(r,j):从D(r,j)到底边的个路径中最佳路径的数字之和
问题:求MaxSum(1,1)
典型的递归问题
D(r,j)出发。下一步只能走D(r+1,j)或者D(r+1,j+1).故对于N行的三角形
if(r==N)
MaxSum(r,j)=D(r,j)
else
MaxSum(r,j)=Max{ MaxSum(r+1,j),MaxSum(r+1,j+1)}+D(r,j);
#include<stdio.h>#include<algorithm>#define MAX 101using namespace std;int D[MAX][MAX];int n;int MaxSum(int i,int j){ if(i==n) return D[i][j]; int x=MaxSum(i+1,j); int y=MaxSum(i+1,j+1); return max(x,y)+D[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]; cout<<MaxSum(1,1)<<endl;}
不过这个会超时,因为很多步都重复计算了
改进:记忆型
没算出一个MaxSum(i,j)就保存起来,下次用到的时候就直接取
#include<stdio.h>#include<algorithm>using namespace std;#define MAX 101int D[MAX][MAX];int n;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(){ int i,j; cin>>n; for(int i=1;i<=n;i++) for(int j=1;j<=i;j++) { cin>>D[i][j]; maxSum[i][j]=-1; } cout<<MaxSum(1,1)<<endl;}
递归转化成递推
//人人为我型递推型动归#include<iostream>#include<algorithm>using namespace std;#define MAX 101int D[MAX][MAX];int n;int maxSum[MAX][MAX];int main(){ int i,j; cin>>n; for(i=1;i<=n;i++) for(j=1;j<=i;j++) cin>>D[i][j]; for(int i=1;i<=n;i++) maxSum[n][i]=D[n][i]; for(int i=n-1;i>=1;i--) for(int j=1;j<=i;j++) maxSum[i][j]=max(maxSum[i+1][j],maxSum[i+1][j+1])+D[i][j]; cout<<maxSum[1][1]<<endl;}再次基础上还可以进行空间的优化,只需一维数组maxSum【100】,就可以存储每一行的数据
//人人为我型递推型动归#include<iostream>#include<algorithm>using namespace std;#define MAX 101int D[MAX][MAX];int n;int* maxSum;int main(){ int i,j; cin>>n; for(i=1;i<=n;i++) for(j=1;j<=i;j++) cin>>D[i][j]; maxSum=D[n];//maxSum指向第n行 for(int i=n-1;i>=1;i--) for(int j=1;j<=i;j++) maxSum[j]=max(maxSum[j],maxSum[j+1])+D[i][j]; cout<<maxSum[1]<<endl; return 0;}递归到动归的一般转化方法
*递归函数有n个参数,就定义一个n维的数组,数组的下标是递归函数参数的取值范围,数组元素的值是递归函数的返回值,这样就可以从边界值开始,逐步填充数组,相当于计算递归函数值的逆过程
动规结题的一般思路
1.将原问题分解为子问题
*把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了,子问题都解决,原问题即解决
*子问题的解一旦求出就会被保存,所以每个子问题只需要求解一次
2.确定状态
*在用动态规划结题是,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状态”,一个“状态下的“值”,就是这个状态对应的子问题的解
3.确定一些初始状态(边界状态)的值
以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值
4.确定状态转移方程
定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之前如何迁移
例题二:最长上升子序列
问题描述
一个数的序列ai,当a 1 < a 2 < ... < a S 的时候,我们称这个序列是上升的。对于给定的一个序列(a 1 , a 2 , ..., a N ),我们可以得到一些上升的子序列(a i1 , a i2 , ..., a iK ),这里1 <= i1 <i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
//"人人为我"递推型动归程序#include<iostream>#include<cstring>#include<algorithm>using namespace std;const int MAXN=1010;int a[MAXN];int maxLen[MAXN];int main(){ int N; cin>>N; for(int i=1;i<=N;i++) { cin>>a[i]; maxLen[i]=1; } for(int i=2;i<=N;i++) { for(int j=1;j<i;j++) { if(a[i]>a[j]) maxLen[i]=max(maxLen[i],maxLen[j]+1); } } cout<<*max_element(maxLen+1,maxLen+N+1); return 0;}动归的三种形式
1)记忆递归型
优点:只经过有用的状态,没有浪费
缺点:可能会因递归层数太深而爆栈,比递推慢
2)“我为人人”递推型
比较符合思考的习惯
3)“人人为我”递推型
在选取最优备选状态的值Fm,Fn,,时,有可能有好的算法或数据结构可以用来显著降低时间复杂度
例三、最长公共子序列
给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。
//"人人为我"递推型动归程序#include<iostream>#include<cstring>#include<algorithm>using namespace std;char sz1[1000];char sz2[1000];int maxLen[1000][1000];int main(){ while(cin>>sz1>>sz2) { int length1=strlen(sz1); int length2=strlen(sz2); int nTmp; int i,j; for(i=0;i<=length1;i++) maxLen[i][0]=0; for(j=0;j<=length2;j++) maxLen[0][j]=0; for(i=1;i<=length1;i++) { for(j=1;j<=length2;j++) { if(sz1[i-1]==sz2[j-1]) maxLen[i][j]=maxLen[i-1][j-1]+1; else maxLen[i][j]=max(maxLen[i][j-1],maxLen[i-1][j]); } } cout<<maxLen[length1][length2]<<endl; } return 0;}
例四、最佳加法表达式
有一个由1...9组成的数字串,问如果将m个加号插入到这个数字串中,在各种可能形成的表达式中,值最小的那个表达式的值是多少
结题思路:
假定数字串长度是n,添玩加号后,表达式的最后一个加号添在第i个数字后面,那么整个表达式的最小值,就等于在前i个数字中插入m-1个加号所能形成的最小值,加上第i+1到n个数字所组成的数的值
设V(m,n)表示在n个数字中插入m个加号所能形成的最小值,那么:
if m=0
V(m,n)=n个数字构成的整数
else if n<m+1
V(m,n)=~
else
V(m,n)=Min{ V(m-1,i) +Num(i+1,n)
//递归#include<stdio.h>#include<string.h>#include<stdlib.h>#include<algorithm>using namespace std;int V[10][10];int Num(int i,int n){ int l=n-i+1; int sum=0,d=1; for(int i=1;i<=l;i++) { sum+=d*n; d=d*10; n--; } return sum;}int ans(int m,int n){ if(V[m][n]!=-1) return V[m][n]; int s=1<<30; for(int i=m;i<n;i++) s=min(s,ans(m-1,i)+Num(i+1,n)); V[m][n]=s; return s;}int main(){ int m; while(~scanf("%d",&m)) { memset(V,-1,sizeof V); for(int i=1;i<=9;i++) V[0][i]=Num(1,i); for(int i=1;i<=m;i++) for(int j=0;j<i+1;j++) V[i][j]=1<<30; printf("%d\n",ans(m,9)); }}
//递推#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int INF=0x3f3f3f3f;const int N=1005;int a[N],num[N][N],dp[N][N];int main(){ int n,m; while(~scanf("%d %d",&n,&m)) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) { num[i][i]=a[i]; for(int j=i+1;j<=n;j++) { num[i][j]=num[i][j-1]*10+a[j]; } } memset(dp,0,sizeof dp); for(int i=1;i<=n;i++) dp[0][i]=num[1][i]; for(int i=1;i<=m;i++) for(int j=i;j<=n;j++) for(int k=i;k<=j;k++) dp[i][j]=min(dp[i][j],dp[i-1][k]+num[k+1][j]); cout<<dp[m][n]<<endl; } return 0;}
例五、神奇的口袋
有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。
John现在有n(1≤n ≤ 20)个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。
//动归解法#include<iostream>#include<string.h>using namespace std;int a[30];int N;int Ways[40][30]; //Ways[i][j]表示从前j种物品里凑出体积i的方法数int main(){ cin>>N; memset(Ways,0,sizeof Ways); for(int i=1;i<=N;i++) { cin>>a[i]; Ways[0][i]=1; } Ways[0][0]=1; for(int w=1;w<=40;w++) { for(int k=1;k<=N;k++) { Ways[w][k]=Ways[w][k-1]; if(w-a[k]>=0) Ways[w][k]+=Ways[w-a[k]][k-1]; } } cout<<Ways[40][N]; return 0;}//“我为人人”型递推解法//定义一维数组 int sum【41】;依次放入物品,计算每次放入物品可达的容积,并在相应空间设置记录,最后判断sum[40]是否可达,到达了几次#define MAX 41int main(){ int n,i,j,input; int sum[MAX]; for(i=0;i<MAX;i++) sum[i]=0; cin>>n; for(i=0;i<n;i++) { cin>>input; for(j=40;j>=1;j--) if(sum[j]>0&&j+input<=40) sum[j+input]+=sum[j]; sum[input]++; } cout<<sum[40]<<endl; return 0;}
例六、0-1背包问题
有N件物品和一个容积为M的背包。第i件物品的体积w[i],价值是d[i]。求解将哪些物品装入背包可使价值总和最大。每种物品只有一件,可以选择放或者不放(N<=3500,M <= 13000)。
用F[i][j]表示取前i种物品,使它们总体积不超过j的最优取法取得的价值总和。要求F[N][M]
边界:
if(w[1]<=j)
F[1][j]=d[1];
else
F[1][j]=0;
用F[i][j]表示取前i种物品,使得它们总体积不超过j的最优解取法取得的价值总和
递推: F[i][j] = max(F[i-1][j],F[i-1][j-w[i]]+d[i]) 取或者不取第i种物品
本题如用记忆型递归,需要一个很大的二维数组,会超内存。注意到这个二维数组的下一行的值,只用到了上一行的正上方及左边的值,因此可用滚动数组的思想,只要一行即可。即可以用一维数组,用“人人为我”递推型动归实现。
//"人人为我"递推型动归程序#include<iostream>#include<cstring>#include<algorithm>#include<stdio.h>using namespace std;int main(){ int f[10000]; //f[i]表示重量为j时最大的价值 int n,C; scanf("%d %d",&n,&C); memset(f,0,sizeof f); int V,W; for(int i=1;i<=n;i++) { scanf("%d %d",&V,&W); for(int j=C;j>=0;j--) { if(j>=V) f[j]=max(f[j],f[j-V]+W); } } printf("%d\n",f[C]);}
- 动态规划!!!动态规划!!!
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- 动态规划
- Unity3DGame学习笔记(2)
- Kattis
- AOP动态代理--基本原理
- org.thymeleaf.dialect.AbstractDialect: method <init>()V not found
- 3. web前端开发分享-css,js提高篇
- 动态规划
- Hadoop学习笔记之——Hadoop构造模块
- Cordova(PhoneGap)基于android平台的二维码处理
- pyhton numpy常用方法汇总
- Hadoop Hive基础sql语法
- JS-笔记
- 多线程高并发内存池队列模型
- 浅谈 C++ 中的 new/delete 和 new[]/delete[]
- unity之刚体组件Rigidbody