算法设计-动态规划(1)
子序列和最大问题
- 子序列和最大
递推方程:d[i] = max{a[i], d[i-1]+a[i]},d[i]表示的是子序列一定会包含第i个元素的最大和,要么是从a[i]开始,要么是在原来的最大序列上再添一个a[i]。最终得到的最大子序列和是max{d[i]}(0<=i<=n)
代码如下:
//b[i]的状态转移方程:b[i]=max{b[i-1]+a[i],a[i]}。最终我们得到的最大子段和为max{b[i], 0<=i<n}
int findmax(int arr[], int n) {
int maxvalue = -200, temp = 0;
for(int i=0; i<n; i++) {
if(temp > 0) {
temp += arr[i]; //这里是用前i个数和第i个数进行比较,如果前i-1个数已经大于0,那么肯定是前i个数的和大于第i个数
}
else temp = arr[i]; //以上是一定包含第i个数的最大值
if(temp > maxvalue) {
maxvalue = temp;
}
}
return maxvalue;
}
-
子序列和的绝对值最大
现求子序列的最大和,再求子序列的最小和,之后两者比较一下 -
子序列的积最大
维护两个数组,一个是最大值,一个是最小值,在遍历到下一个元素的时候,分别都要记录最大值和最小值(防止负负得正之后出现了最大值) -
两段重合不连续子序列最大和
要维护两个数组,一个是从前向后记录最大值,一个是从后向前记录最大值,然后把这两个数组的和相加找一个最大值
int solve(int arr[], int n) {
int dp[Max], lt[Max], rt[Max], maxvalue;
dp[0] = lt[0] = dp[n+1] = rt[n+1] = maxvalue = -INF;
for(int i=1; i<=n; i++) { //dp记录的是包含第i个元素的序列和最大值
dp[i] = max(dp[i-1]+arr[i], arr[i]);
}
for(int i=1; i<=n; i++) {
lt[i] = max(dp[i], lt[i-1]); //这个找到的是前i个元素的最大值
}
for(int i=n; i>=1; i--) {
dp[i] = max(dp[i+1]+arr[i], arr[i]);
}
for(int i=n; i>=1; i--) {
rt[i] = max(rt[i+1], rt[i]);
}
for(int i=0; i<=n; i++) {
if(maxvalue > lt[i] + rt[i+1]) {
maxvalue = lt[i] + rt[i+1];
}
}
return maxvalue;
}
- 子序列和最大且长度受限
子序列和长度不超过m
这道题的方程就是sum[i]-min(sum[j])(j<=i).维护一个单调队列,这个单调队列的目标就是快速找到前i个元素中的最小值,这个单调队列记录的是前缀和,如果现在来了一个前缀和,比在队列中的前缀和要小,那么队列中的元素就没有必要继续存储了。
#include <cstdio>
#include <list>
using namespace std;
list<int> queue;
int sum[100];
int main() {
int n, maxn;
for(int i=1; i<=n; i++) { //记录的是前缀和
scanf("%d", &sum[i]);
sum[i] += sum[i-1];
}
queue.push_front(0);
for(int i=1; i<=n; i++) {
if(!queue.empty() && sum[queue.front()] > sum[i]) {
queue.pop_front();
}
queue.push_front(i); //后进来的元素放到列表的头部,最小的元素始终在队列的尾部
while(!queue.empty() && i-queue.back() > m) { //如果长度已经超过m
queue.pop_back();
}
maxn = max(maxn, sum[i] - sum[queue.back()]);
}
cout<<maxn<<endl;
}
我
- 子矩阵和最大
子矩阵和最大是一个二维问题,因此要转化为一维问题,一维问题就是子序列和最大问题。那么如何转化成为一维问题呢,就是把每一行元素都加到第一行上面,这样得到的就是一维序列。
//
// main.cpp
// homework3.5
//
// Created by dongyu on 2019/10/30.
// Copyright © 2019 dongyu. All rights reserved.
//
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define Max 105
using namespace std;
//b[i]的状态转移方程:b[i]=max{b[i-1]+a[i],a[i]}。最终我们得到的最大子段和为max{b[i], 0<=i<n}
int findmax(int arr[], int n) {
int maxvalue = -200, temp = 0;
for(int i=0; i<n; i++) {
if(temp > 0) {
temp += arr[i]; //这里是用前i个数和第i个数进行比较,如果前i-1个数已经大于0,那么肯定是前i个数的和大于第i个数
}
else temp = arr[i]; //以上是一定包含第i个数的最大值
if(temp > maxvalue) {
maxvalue = temp;
}
}
return maxvalue;
}
int main(int argc, const char * argv[]) {
// insert code here...
int n, maxvalue = -200;
int input[Max][Max], arr[Max];
scanf("%d", &n);
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
scanf("%d", &input[i][j]);
}
}
for(int i=0; i<n; i++) {
memset(arr, 0, sizeof(arr));
for(int j=i; j<n; j++) {//从第i行开始,到第j行结束,将这些行每一行元素都加到第一行
for(int k=0; k<n; k++) {
arr[k] += input[j][k];
}
int temp = findmax(arr, n);
if(temp > maxvalue) {
maxvalue = temp;
}
}
}
cout<<maxvalue<<endl;
return 0;
}