王道机试 第十二章 动态规划 12.2 最大连续子序列和

王道机试 第十二章 动态规划 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 i1)表示以 a i a_i ai结尾的所有连续子序列的和的最大值。

(2)第二步

  • 推导递推方程
  • 考察数组 d p [ i ] ( i ≥ 1 ) dp[i](i \geq 1) dp[i](i1)每一项的关系。
  • 由题意知,对于 d p [ i ] dp[i] dp[i]( i ≥ 2 i \geq 2 i2),即以 a i a_i ai结尾的所有连续子序列的和的最大值,其构成有且仅有两种可能其一是取前面的元素,其二是不取前面的元素。
  • 若取前面的元素,由于 d p [ i − 1 ] dp[i - 1] dp[i1]为当前最优解,而当前序列以 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[i1]+a[i],a[i])( i ≥ 2 i \geq 2 i2)

(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[i1]+a[i],a[i]),且 i ≥ 2 i \geq 2 i2,故而我们需要求出数列的第一项 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值