王道机试 第十二章 动态规划 12.2最大连续子序列和
12.2 最大连续子序列和
例题12.2 最大序列和(清华大学复试上机题)
- 动态规划思路
(1)第一步
- 设变量,求什么设什么。
- 题目希望求出以元素 a j a_{j} aj结尾的连续子序列{ a 1 , a 2 , … , a j a_1, a_2, \dots, a_j a1,a2,…,aj},使得这个连续子序列的和最大。
- 则令 d p [ i ] dp[i] dp[i]( i ≥ 1 i \geq 1 i≥1)表示以 a i a_i ai结尾的所有连续子序列的和的最大值。
(2)第二步
- 推导递推方程。
- 考察数组 d p [ i ] ( i ≥ 1 ) dp[i](i \geq 1) dp[i](i≥1)每一项的关系。
- 由题意知,对于 d p [ i ] dp[i] dp[i]( i ≥ 2 i \geq 2 i≥2),即以 a i a_i ai结尾的所有连续子序列的和的最大值,其构成有且仅有两种可能:其一是取前面的元素,其二是不取前面的元素。
- 若取前面的元素,由于 d p [ i − 1 ] dp[i - 1] dp[i−1]为当前最优解,而当前序列以 a i a_i ai结尾,故此时取得最大值。
- 若不取前面的元素,则最优解为 a i a_i ai
- 综上所述, d p [ i ] = m a x ( d p [ i − 1 ] + a [ i ] , a [ i ] ) dp[i] = max(dp[i - 1] + a[i], a[i]) dp[i]=max(dp[i−1]+a[i],a[i])( i ≥ 2 i \geq 2 i≥2)
(3)第三步
- 确定初始值
- 由于 d p [ i ] = m a x ( d p [ i − 1 ] + a [ i ] , a [ i ] ) dp[i] = max(dp[i - 1] + a[i], a[i]) dp[i]=max(dp[i−1]+a[i],a[i]),且 i ≥ 2 i \geq 2 i≥2,故而我们需要求出数列的第一项 d p [ 1 ] dp[1] dp[1],从而根据初始值和递推方程,最终计算出 d p [ n ] dp[n] dp[n]。
- 显然, d p [ 1 ] = a [ 1 ] dp[1] = a[1] dp[1]=a[1](序列中只有一个元素)。
C++代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1000005;
long long dp[maxn]; // 以a[k]为末尾的最大连续子序列和
long long a[maxn]; // a[1]~a[n]代表数列的N个元素
int main()
{
int n;
while (cin >> n){
memset(a, 0, sizeof(a));
for (int i = 1; i <= n; i++){
cin >> a[i];
}
dp[1] = a[1]; // 初始值
for (int i = 2; i <= n; i++){ // 递推方程
dp[i] = max(a[i], dp[i - 1] + a[i]); // 有可能是只取第i个元素,也有可能在原来的基础上加上第i个元素
}
int maxx = dp[1];
for (int i = 2; i <= n; i++){ // 取dp数组中的最大元素
if (maxx < dp[i]) maxx = dp[i];
}
cout << maxx << endl;
}
return 0;
}
例题12.3 最大子矩阵(北京大学复试上机题)
- 思路
- 将此问题转换为最大连续子序列和问题:子矩阵的元素之和相当于对其每列元素求和,将这些值存入数组中,再相加。那么就相当于先计算第i行到j行,对于第k列的元素值之和,把这些数值看做一个序列,再利用动态规划求解最大连续子序列和问题即可。
- 最终的答案直接枚举三维dp数组中的元素值,取最大者即可。
C++代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 105;
int a[maxn][maxn]; // 存储矩阵
int b[maxn][maxn][maxn]; // 从第i行到第j行,第k列的元素之和
int dp[maxn][maxn][maxn]; // 从第i行到第j行的最大连续子序列和,存到第三维中
int main()
{
int n;
while (cin >> n){
memset(a, 0, sizeof(a));
for (int i = 1; i <= n; i++){ // input
for (int j = 1; j <= n; j++) cin >> a[i][j];
}
memset(b, 0, sizeof(b)); // 辅助数组,记录从第i行到第j行,第k列的元素之和
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
for (int k = 1; k <= n; k++){
for (int m = i; m <= j; m++)
if (i != j) b[i][j][k] += a[m][k];
else b[i][j][k] = a[m][k];
}
}
}
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++){ // 动态规划
for (int j = 1; j <= n; j++){
dp[i][j][1] = b[i][j][1]; // 初始值
for (int k = 2; k <= n; k++){ // 递推方程
dp[i][j][k] = max(b[i][j][k], b[i][j][k] + dp[i][j][k - 1]);
}
}
}
int maxx = dp[1][1][1]; // 最大值
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
for (int k = 1; k <= n; k++){
maxx = max(maxx, dp[i][j][k]);
}
}
}
cout << maxx << endl;
}
return 0;
}
习题12.2 最大连续子序列(浙江大学复试上机题)
- 左右端点的求法
- 在最大连续子序列的基础上,由于 d p [ k ] dp[k] dp[k]的定义为以 a k a_{k} ak结尾的连续子序列的最大和,那么可由此固定右端点。再从右端点向左扩展,直到序列和等于 m a x max max d p [ i ] dp[i] dp[i],即求出最优端点。
- 注意
- 题目要求输出的是左右端点的数值,不是位置!(卡了好久)
C++代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 10005;
long long dp[maxn]; // 以a_k结尾的连续子序列的最大值
long long a[maxn]; // 存储输入的元素
int flag;
int main()
{
int n;
int left, right; // answer
while (cin >> n && n){ // n==0时用例不被处理
memset(a, 0, sizeof(a));
flag = 0;
for (int i = 1; i <= n; i++){ // input
cin >> a[i];
if (a[i] >= 0) flag = 1;
}
if (flag == 0){
cout << 0 << " " << a[1] << " " << a[n] << endl;
continue;
}
memset(dp, 0, sizeof(dp));
dp[1] = a[1]; // 初始化
left = 1; // answer
for (int i = 2; i <= n; i++){ // dp[i] = max(dp[i - 1] + a[i], a[i])
dp[i] = max(dp[i - 1] + a[i], a[i]);
}
long long maxx = dp[1];
right = 1;
for (int i = 2; i <= n; i++){
if (maxx < dp[i]){
maxx = dp[i];
right = i;
}
}
long long sum = 0;
for (int i = right; ;i--){
sum += a[i];
if (sum == maxx){
left = i;
break;
}
}
cout << maxx << " " << a[left] << " " << a[right] << endl;
}
return 0;
}