算法入门 - 动态规划

例题1 数字三角形

        7
       3 8
     8  1  0
   2  7  4   4
4   5  2   6   5
在上面的数字三角形中寻找一条从顶部到底边的路径。使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。只需要求出这个最大和即可,不必给出具体路径。
1<三角形的行数<=100,数字为0-99
输入
5        //三角形的行数
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
要求输出最大和

思路 递归

D(r,j):第r行第j个数字
MaxSum(r,j):从D(r,j)到底边的各条路径中,最佳路径的数字之和
我们现在要求MaxSum(1,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) }

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

改进 避免重复计算

没算出一个MaxSum(i,j)就保存起来,下次可以直接用

#include <iostream>
#include <algorithm>
#define MAX 101

using namespace std;

int 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)
		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];
			maxSum[i][j] = -1;
		}		
	cout << MaxSum(1, 1) << endl;
	
	return 0;
}

将递归转成递推

#include <iostream>
#include <algorithm>
#define MAX 101

using namespace std;

int 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;	
	return 0;
}

空间优化

直接用二维数组D的第n行替代maxSum即可

#include <iostream>
#include <algorithm>
#define MAX 101

using namespace std;

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

例题2 最长上升子序列

假设我们有一个序列 b i,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们也可以从中得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N,但必须按照从前到后的顺序。比如,对于序列(1, 7, 3, 5, 9, 4, 8),我们就会得到一些上升的子序列,如(1, 7, 9), (3, 4, 8), (1, 3, 5, 8)等等,而这些子序列中最长的(如子序列(1, 3, 5, 8) ),它的长度为4,因此该序列的最长上升子序列长度为4。
输入:第一行是序列的长度(1<=N<=1000),第二行给出序列中的N个整数
输出:最长上升子序列的长度

解题思路

①找子问题:求以ak(k=1,2,3...N)为终点的最长上升子序列的长度
②子问题只和一个变量---数字的位置有关,因此序列中ak的位置就是“状态”,一共有N个状态

③找出状态转移方程 
maxLen(k)表示以ak为"终点"的最长上升子序列的长度
k=1, maxLen(1) = 1
k>1,maxLen(k) = max{ maxLen(i): 1 <= i < k 且 ai < ak} + 1,如果ai(i1<i<k)都大于ak,那么maxLen(k)=1

#include <iostream>
#include <algorithm>
#define MAXN 1010

using namespace std;

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

例题3 背包问题

有N件物品和一个容积为M的背包。第i件物品的体积w[i]价值为d[i]。求解将哪些物品装入背包可使价值总和最大。每种物品只有一件,可以选择放或者不放(N<=3500,M<=13000)。

分析

用 dp[i] 表示容积还剩 i,用最优法取得的价值总和。要求dp[M]
遍历N件物品,对于第i个物品(体积为w[i],价值为d[i]),更新dp[w[i]] ~ dp[M]

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int maxn = 13001;
int dp[maxn];
int cost[3500], weight[3500];

int main() {
	int i,j;
	int N,M;
	scanf("%d %d", &N, &M);
	memset(dp, 0, sizeof(dp));
	for(i = 1; i <= N; ++i) {
		scanf("%d %d", &cost[i], &weight[i]);
	}
	for(i = 1; i <= N; ++i) {
		for(j = M; j >= cost[i]; --j){
			dp[j] = max(dp[j], dp[j-cost[i]] + weight[i]);
		}
	}
	printf("%d\n", dp[M]);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

漂流の少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值