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