动态规划经典题目总结

数字三角形问题

题意
给定一个由n行数字组成的数字三角形\。试设计一个算法,计算出从三角形的顶至底的一条路径,使该路径经过的数字总和最大。
对于给定的由n行数字组成的数字三角形,计算从三角形的顶至底的路径经过的数字和的最大值。

Input
输入数据的第1行是数字三角形的行数n,1≤n≤100。接下来n行是数字三角形各行中的数字。所有数字在0…99之间。

Output
输出数据只有一个整数,表示计算出的最大值.

样例输入
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
样例输出
30

思路实现:
刚看到这个题,第一反应是递归…,要不是dp的例题我就用递归来做了,不过很明显递归肯定是会超时的。就往dp的思路上靠,感觉这个题是入门级题目,思路比较简单,从最下面一行开始逐一往上进行计算

代码:

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX 101;
int dp[101][101];
int n;
int Sum[101][101];
int main() 
{
    int i, j;
    cin >> n;
    for (i = 1; i <= n; i++)
        for (j = 1; j <= i; j++)
            cin >> dp[i][j];
    for (int i = 1; i <= n; i++)
        Sum[n][i] = dp[n][i];
    for (int i = n - 1; i >= 1; i--)
        for (int j = 1; j <= i; j++)
            Sum[i][j] =
            max(Sum[i + 1][j], Sum[i + 1][j + 1]) + dp[i][j];
            cout << Sum[1][1] << endl;
}

最长公共子序列

题意
给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。

Sample Input

abcfbc abfcab
programming contest
abcd mnp

Sample Output

4
2
0

思路实现:
这题老师上课也讲了,但我感觉理解的不到位,自己再总结一下

①确定状态:
设F[x][y]表示S[1…x]与T[1…y]的最长公共子序列的长度

②确定状态转移方程和边界条件:
D分三种情况来考虑:
S[x]不在公共子序列中:该情况下FxLy]=F[x-1][y]
T[y]不在公共子序列中:该情况下F[x][y]=F[x-1][y-1]
S[x]=Ty],S[x]与T[y]在公共子序列中:该情况下,F[x][y]=F[x-1][y-1]+1
F[x][y]取上述三种情况的最大值。

综上:
状态转移方程:Fx[y]=max{F[x-1ILy], FLXILY-1],FLx-11[y-11+1)其中第三种情况要满足SLx]=T[y];
边界条件:F[0][y]=0,F[x][0]=0

③程序实现:
计算F[x][y]时用到F[x-1][y-1],F[x-1][y],F[x][y-1]这些状态,它们要么在F[x][y]的上一行,要么在F[x][y]的左边。因此预处理出第0行,然后按照行从小到大、同一行按照列从小到大的顺序来计算就可以用选代法计算出来。
代码:

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int MAXN = 5005;
string S, T;
int F[MAXN][MAXN];
int main()
{
	cin >> S;
	cin >> T;
	int ls = S.length(), lt = T.length();
	for (int i = 1; i <= ls; i++)
		for (int j = 1; j <= lt; j++)
		{
			F[i][j] = max(F[i - 1][j], F[i][j - 1]);
			if (S[i - 1] == T[j - 1])
				F[i][j] = max(F[i][j], F[i - 1][j - 1] + 1);
		}

	cout << F[ls][lt] << endl;
	return 0;
}

最大上升子序列

题意
一个数的序列ai,当a1 < a2 < … < aS的时候,我们称这个序
列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK<= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).你的任务,就是对于给定的序列,求出最长上升子序列的长度。

输入
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给
出序列中的N个整数,这些整数的取值范围都在0到10000。

输出
最长上升子序列的长度。

输入样例
7
1 7 3 5 9 4 8

输出样例
4

思路实现:
这也是典型的例题,自己总结一下子,加深下理解。

1.找子问题
“求序列的前n个元素的最长上升子序列的长度”是个子问题,但这样分解子问题,不具有“无后效性”假设F(n) = x,但可能有多个序列满足F(n) = x。有的序列的最后一个元素比 an+1小,则加上an+1就能形成更长上升子序列;有的序列最后一个元素不比an+1小……以后的事情受如何达到状态n的影响,不符合“无后效性”“求以ak(k=1, 2, 3…N)为终点的最长上升子序列的长度,一个上升子序列中最右边的那个数,称为该子序列的“终点”。
虽然这个子问题和原问题形式上并不完全一样,但是只要这N个子问题都解决了,那么这N个子问题的解中,最大的那个就是整个问题的解。

  1. 确定状态:
    子问题只和一个变量-- 数字的位置相关。因此序列中数的位置k 就是“状态”,而状态 k 对应的“值”,就是以ak做为“终点”的最长上升子序列的长度。状态一共有N个。

  2. 找出状态转移方程:
    maxLen (k)表示以ak做为“终点”的
    最长上升子序列的长度那么:
    初始状态:maxLen (1) = 1
    maxLen (k) = max { maxLen (i):1<=i < k 且 ai < ak且 k≠1 } + 1
    若找不到这样的i,则maxLen(k) = 1
    maxLen(k)的值,就是在ak左边,“终点”数值小于ak ,且长度最大的那个上升子序列的长度再加1。因为ak左边任何“终点”小于ak的子序列,加上ak后就能形成一个更长的上升子序列。

代码


#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) 
	{ 
		//每次求以第i个数为终点的最长上升子序列的长度
		for( int j = 1; j < i; ++j) 
		//察看以第j个数为终点的最长上升子序列
		if( a[i] > a[j] )
			maxLen[i] = max(maxLen[i],maxLen[j]+1); 
	}
	cout << * max_element(maxLen+1,maxLen + N + 1 );
	return 0;
} 

最大子矩阵和

题意
一个MN的矩阵,找到此矩阵的一个子矩阵,并且这个子矩阵的元素的和是最大的,输出这个最大的值。
例如:3
3的矩阵:
-1 3 -1
2 -1 3
-3 1 2
和最大的子矩阵是:
3 -1
-1 3
1 2

Input
第1行:M和N,中间用空格隔开(2 <= M,N <= 500)。
第2 - N + 1行:矩阵中的元素,每行M个数,中间用空格隔开。(-10^9 <= M[i] <= 10^9)

Output
输出和的最大值。如果所有数都是负数,就输出0。

思路
也是经典例子,解法与动态规划经典题目——最大连续子序列之和题目思想一样,只不过是二维空间上的拓展。N*N矩阵用二维数组a[N][N]表示。通过思考可以发现,这道题目与动态规划经典题目——最大连续子序列之和非常相似,只不过动态规划经典题目——最大连续子序列之和它是一维的问题,我们可以把每列的元素进行合并为一个元素(可以定义二维数组sum[][],其中sum[i]表示前i行每列数字相加的和,所以sum[j][] - sum[i][]为一维向量),这样运用动态规划经典题目——最大连续子序列之和的思想了。

代码:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N = 1000;
ll arr[N][N];
ll dp[N][N];
int m, n;
int main()
{
	while (cin>>m>>n)
	{
		memset(dp, 0, sizeof(dp));
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= m; j++)
			{
				cin >> arr[i][j];
				dp[i][j] = dp[i - 1][j] + arr[i][j];
			}
		}
		ll ans = arr[1][1];
		for (int i = 1; i <= n; i++)
			for (int j = i; j <= n; j++) 
			{
				ll sum = 0;
				for (int k = 1; k <= m; k++)
				{
					sum += dp[j][k] - dp[i - 1][k]; 
					if (sum < 0)
						sum = 0;
					if (ans < sum)
						ans = sum;
				}
			}
		cout << ans << endl;
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 0-1背包问 已知条件:有一个背包,可以装载一定重量的物品;有n个物品,每个物品有重量和价值两个属性;背包最大可承受重量为W;求解将哪些物品装入背包可使这些物品的总重量不超过背包的最大承重且总价值最大。 2. 问建模 将这个问建模成一个决策树问,每个节点表示一个状态,每个状态有两个决策:装入物品或不装入物品,直到达到叶子节点,计算出能够装入背包的物品的最大价值。 3. 算法设计 采用回溯算法,从根节点开始搜索,依次考虑每个物品是否选择装入背包,记录背包的剩余重量和当前的总价值,每次选择后,继续向下搜索,直到达到终止条件(背包装满或者搜索完所有物品),在搜索的过程中,记录下当前的最大价值。 4. 算法分析 时间复杂度:O(2^n),n为物品的数量。 空间复杂度:O(n),需要保存每个物品的重量和价值。 5. 编码实现 Python代码如下: ```python def backtrack(i, weight, value, n, W, weights, values, max_value): if weight > W: # 背包装不下了,直接返回 return if i == n: # 物品都考虑完了 max_value[0] = max(max_value[0], value) # 更新最大价值 return # 不选第i个物品 backtrack(i+1, weight, value, n, W, weights, values, max_value) # 选第i个物品 backtrack(i+1, weight+weights[i], value+values[i], n, W, weights, values, max_value) def knapsack(W, weights, values): n = len(weights) max_value = [0] backtrack(0, 0, 0, n, W, weights, values, max_value) return max_value[0] weights = [2, 2, 6, 5, 4] values = [6, 3, 5, 4, 6] W = 10 print(knapsack(W, weights, values)) ``` 6. 测试数据 测试数据如下: | weights | values | W | max_value | | ------- | ------ | - | --------- | | [2, 2, 6, 5, 4] | [6, 3, 5, 4, 6] | 10 | 15 | 7. 程序运行结果 程序运行结果如下: ``` 15 ``` 8. 分析实验结果是否符合预期,如果不符合,分析可能的原因 实验结果符合预期。 9. 总结 0-1背包问是一个经典动态规划,这里采用了回溯算法来解决。回溯算法是一种基于深度优先搜索的算法,对于每个状态,都要进行两种决策:选择或不选择。回溯算法的时间复杂度较高,但是对于一些特定的问,回溯算法可以有效地解决。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值