赞
踩
目录
1.将原问题分解为子问题
- 把原问题分解为若千个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)
- 子问题的解一旦求出就会被保存,所以每个子问题只需求解一次。
2.确定状态
- 将和子问题相关的各个变量的一组取值,称之为一个“状态”。一个“状态”对应于一个或多个子问题,所谓某个“状态”下的“值”就是这个“状态”所对应的子问题的解。
- 所有“状态”的集合,构成问题的“状态空间”,“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。
- 整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。
在数字三角形的例子里,一共有N*(N+1)/2个数字,所以这个问题的状态空间里一共就有N* (N+1)/2个状态。数字三角形中每个“状态”只需要经过一次,且在每个状态上作计算所花的时间都是和N无关的常数。
用动态规划解题,经常碰到的情况是,K个整型变量能构成一个状态(如数字三角形中的行号和列号这两个变量构成“状态”)。如果这K个整型变量的取值范围分别是N1, N2, ..... Nk,那么我们就可以用一个K维的数组array[N1] [N2.]....Nk]来存储各个状态的“值”。这个值”未必就是一个整数或浮点数,可能是需要一个结构才能表示的,那么array就可以是一个结构数组。一个“状态”下的‘‘值’通常会是一个或多个子问题的解。
3.确定一些初始状态(边界状态)的值
以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值。
4.确定状态转移方程
定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移,即如何从一个或多个“值”已知的“状态”,求出另一个“状态”的“值”(“人人为我”递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。
1.描述
给出两个字符串,求最长的公共子序列的长度。
公共子序列:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。
2.样例输入:
abcfbc abfcab
programming
contest
abcdmnp
3.样例输出:
4
2
0
(1)找出子问题
输入两个串s1 ,s2,
设MaxLen(i,j)表示:s1的左边i个字符形成的子串,与s2左边的j个字符形成的子串的最长公共子序列的长度(i,j从0开始)
(2)确定状态
MaxLen(i,j)就是本题的“状态”
假定len1 = strlen(s1),len2 = strlen(s2)那么题目就是要求MaxLen(len1,len2)
(3)状态转移方程
MaxLen(n,0) =0 ( n=0...len1)
MaxLen(0,n) =0 ( n=0...len2)
通过观察可以得到递推公式:
- if(s1[i-1] == s2[j-1] )//s1的最左边字符是s1[0]
- MaxLen(i,j) = MaxLen(i-1,j-1) + 1;
- else
- MaxLen(i,j) = Max(MaxLen(i,j-1),MaxLen(i-1,j));
4.代码
时间复杂度O(mn),m,n是两个字串长度
- #include <iostream>
- #include <cstring>
- 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 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.描述
由n个整数(包含负整数)组成的序列a1,a2,...,an,求该序列子段和的最大值。
规定当所有整数均为负值时定义其最大子段和为0。
否则所求的最大值为
例如,当(a1,a2, ……a7,a8)=(1,-3, 7,8,-4,12, -10,6)时, 最大子段和为:7+8-4+12=23
2.思路
设b[j]是从a[1]到a[j]位置的最大子段和(必须以a[j]结尾)
当b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j]
| 1 | 2 | 3 | 4 | 5 | 6 |
a[i] | -2 | 11 | -4 | 13 | -5 | -2 |
b(初值=0) | -2 | 11 | 7 | 20 | 15 | 13 |
sum | 0 | 11 | 11 | 20 | 20 | 20 |
3.代码
- #include <iostream>
- #include <algorithm>
- using namespace std;
- const int MAX=10000;
- int main()
- {
- int n;
- cin>>n;
- int a[MAX],b[MAX];
- for(int i=1;i<=n;i++){
- cin>>a[i];
- b[i]=0;//以a[i]为结尾的最大子段和
- }
-
- for(int i=1;i<=n;i++){
- if(b[i-1]>0)
- b[i]=b[i-1]+a[i];
- else
- b[i]=a[i];
- }
- int m=*max_element(b+1,b+n+1);
- cout<<max(m,0)<<endl;
- return 0;
- }
1.描述
一个数的序列ai,当a1 < a2 < ... < ag的时候,我们称这个序列是上升的。比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。这些子序列中最长的长度是4,比如子序列(1,3,5,8)。
2.输入
第一行是序列的长度N (1 <= N<=1000)。给出序列中的N个整数,这些整数的取值范围都在0到10000。
3.输出
最长上升子序列的长度
4.输入样例
7
1 7 3 5 9 4 8
5.输出样例
4
6.思路
(1)子问题
求以a[k] (k=1,2,3…N) 为终点的最长上升子序列的长度
一个上升子序列中最右边的那个数,称为该子序列的“终点”。
虽然这个子问题和原问题形式上并不完全一样,但是只要这N个子问题都解决了,那么这N个子问题的解中,最大的那个就是整个问题的解。
(2)确定状态
子问题只和一个变量——数字的位置相关。
因此序列中数的位置k就是“状态”,而状态k对应的“值”,就是以a[k]为“终点”的最长上升子序列的长度。状态一共有N个。
(4)状态转移方程
maxLen (k) 表示以a[k]为“终点”的最长上升子序列的长度,那么:
初始状态: maxLen (1) = 1
- maxLen(k)=max{maxLen(i):1<=i<k且a[i]<a[k]且k≠1}+1。
- maxLen(k)就是起点在a[k]左边,终点数值小于a[k],且长度最大的那个上升子序列的长度再加1。此处的的1就是a[k]本身,因为a[k]左边任何终点小于a[k]的子序列,加上a[k]后就能形成一个更长的上升子序列。
- 若找不到这样的i,则maxLen(k) = 1
为了保证上升子序列尽可能的长,那么就有 dp[ i ] 尽可能的大, 但是在保证 dp[ i ] 尽可能大的基础上,还必须满足序列的上升; 所以 dp[ i ] = max ( 1 , dp[ j ] + 1 ) { j < i && a[j] < a[i] } 。
将 j 从 1 遍历到 i - 1,在这之间,找出尽可能大的dp[ i ]即为最长上升子序列的长度
dp[n] 不一定是最长的子序列,n是数的个数,例如序列 [ 2, 3, 4, 5, 1 ],dp[5] = 1(由子序列[1]构成),然而 dp[4] = 4(由子序列 [2,3,4,5] 构成) )
举个例子:一个序列(7, 9, 6, 10, 7, 1, 3)分别为 (a1, a2, a3, a4, a5, a6, a7)
7.代码:
max(a,b):返回a,b两者之间的较大值
max_element(r, r+6):返回数组r中[0, 6)之间的最大值的迭代器
- #include <iostream>
- #include <algorithm>
- using namespace std;
-
- const int MAX=1010;
- int a[MAX];
- int maxLen[MAX];
- int main()
- {
- int n;
- cin>>n;
- for(int i=1;i<=n;i++){
- cin>>a[i];
- maxLen[i]=1;
- }
- //每次求以第i个数为终点的最长上升子序列的长度
- for(int i=2;i<=n;i++){
- //查看以第j个数为终点的最长上升子序列
- 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.描述
在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。
三角形的行数大于1小于等于100,数字为0- 99
2.输入格式:
5//三角形行数。下面是三角形
3.输出格式:
30//路径最大和
用二维数组的下三角存放数字三角形(数字三角形的最后一行可以直接存储到D[i][j]中)
D[i][j] :第i行第j个数字(i,j从1开始算)
MaxSum(i, j):从D[i][j]到底边的各条路径长
问题转化为求MaxSum(1,1)
D[i][j]出发,下一步只能走D[i+1][j]或者D[i+1][j+1]
(1)故对于N行的三角形,运用递归:
- if (i==N)
- MaxSum(i,j) = D[i][j];//最后一行
- else
- MaxSum(i,j) = Max{MaxSum(i+1,j),MaxSum(i+1,j+1)}+ D[i][j];
因此,数字三角形的递归程序如下
- #include <iostream>
- #include <algorithm>
-
- #define MAX 101
- using 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;
- return 0;
- }
但这个程序里有大量重复计算,时间复杂度为2^n
(2)改进:
如果每算出一个MaxSum(i,j)就保存起来,下次用到其值的时候直接取用,则可免去重复计算。因为三角形的数字总数是n*(n+1)/2,那么可以用O(n^2)时间完成计算。
- #include <iostream>
- #include <algorithm>
-
- #define MAX 101
- using namespace std;
-
- int D[MAX][MAX];
- int maxSum[MAX][MAX];
- int n;
-
- 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(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;
- }
(3)递归转为递推
递推过程:
空间优化:
没必要用二维maxSum数组存储每一个MaxSum(rj),只要从底层一行行向上递推,那么只要一维数组maxSum[100]即可,即只要存储一行的MaxSum值就可以。
进一步考虑,连maxSum数组都可以不要,直接用D的第n行替代maxSum即可。
节省空间,时间复杂度不变
- #include <iostream>
- #include <algorithm>
- using namespace std;
- #define MAX 101
- int 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];
- 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;
- }
详解在我的另一篇博客
1.描述
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
2.输入
输入的第一行是一个整数N(2<=N<=100),表示同学的总数。 第二有N个整数,用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。
3.输出
输出最少需要几位同学出列。
4.样例输入
8 186 186 150 200 160 130 197 220
5.样例输出
4
6.思路
选中队列中的一个学生,以该学生为核心,分别求出其左侧的最长递增子序列和其右侧的最长递减子序列,两者相加,再减去1就是以该同学为中心的合唱队的人数
我们只需把每个学生都作为中心遍历一遍,就能得出人数最多的合唱队形,再把总人数减去合唱人数就是需要剔除的人数。
7.代码
- #include <iostream>
- #include <algorithm>
- #include <cstdio>
-
- using namespace std;
- const int MAX=110;
- int hign[MAX];
- int maxLen[MAX];
- int minLen[MAX];
- int s1[MAX]={0};
- int s2[MAX]={0};
- int main()
- {
- int n,sum=0;
- scanf("%d",&n);
- for(int i=1;i<=n;i++){
- scanf("%d",&hign[i]);
- maxLen[i]=1;
- minLen[i]=1;
- }
- //以第k个学生为中心,k=1~n
- for(int k=2;k<=n;k++){
- for(int i=2;i<=k;i++){
- for(int j=1;j<i;j++)
- if(hign[i]>hign[j])
- maxLen[i]=max(maxLen[i],maxLen[j]+1);
- s1[i]=max(maxLen[i],s1[i]);
- }
-
- for(int i=k+1;i<=n;i++){
- for(int j=k;j<i;j++)
- if(hign[i]<hign[j])
- minLen[i]=max(minLen[i],minLen[j]+1);
- s2[i]=max(minLen[i],s2[i]);
- }
-
- }
-
- for(int i=1;i<=n;i++)
- sum=max(s1[i]+s2[i]-1,sum);
- printf("%d",n-sum);
-
- return 0;
- }
1.描述
设有一个三角形的数塔,顶点为根结点,每个结点有一个整数值。从顶点出发,可以向左走或向右走,如图所示:
若要求从根结点开始,请找出一条路径,使路径之和最大,只要输出路径的和。
2.输入
第一行为n(n<50),表示数塔的层数
从第2行至n+1行,每行有若干个数据,表示数塔中的数值。
3.输出
最大的路径值。
4.样例输入
5 13 11 8 12 7 26 6 14 15 8 12 7 13 24 11
5.样例输出
86
6.思路
用一个二维数组a存储数塔的原始数据(其实我们只使用数组a一半的空间,一个下三角矩阵)
用一个二维数组dp存储每一次决策过程中的结果(也是一个下三角矩阵)
初始化dp,将a的最后一层拷贝到dp中:dp[n][j] = a[n][j] (j = 1, 2, …, n) 其中,n为数塔的层数
递归填上dp数组的值,dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + a[i][j],最后的结果保存在dp[0][0]中
7.代码
- #include <iostream>
- #include <cstdio>
- using namespace std;
-
- const int MAX=55;
- int a[MAX][MAX];
- int dp[MAX][MAX];
- int main()
- {
- int n,tmax;
- scanf("%d",&n);
- for(int i=1;i<=n;i++)
- for(int j=1;j<=i;j++)
- scanf("%d",&a[i][j]);
-
- //初始化dp数组最后一行
- for(int i=1;i<=n;i++)
- dp[n][i]=a[n][i];
-
- //递归从下到上求dp数组
- for(int i=n;i>0;i--){
- for(int j=1;j<=i;j++){
- tmax=max(dp[i+1][j],dp[i+1][j+1]);
- dp[i][j]=tmax+a[i][j];
- }
- }
-
- printf("%d",dp[1][1]);
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。