51nod1524 最大子段和V2

## 题干

N个整数组成的序列a[1],a[2],a[3],…,a[n],你可以对数组中的一对元素进行交换,并且交换后求a[1]至a[n]的最大子段和,所能得到的结果是所有交换中最大的。当所给的整数均为负数时和为0。
例如:{-2,11,-4,13,-5,-2, 4}将 -4 和 4 交换,{-2,11,4,13,-5,-2, -4},最大子段和为11 + 4 + 13 = 28。

Input
第1行:整数序列的长度N(2 <= N <= 50000) 第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)
Output
输出交换一次后的最大子段和。
Sample Input
7
-2 11 -4 13 -5 -2 4

Sample Output

28

一点题外话

这道题真的很不起眼qwq

一开始做的时候各位大佬都是各显神通,然鹅结果不如人意,然后去网上找题解,结果题解里的显然显然不是那么正确...(https://www.cnblogs.com/hbhszxyb/p/13130945.html)。

能AC完全是数据水,于是狠狠地暴露了一波,还好咱当时就是没看懂那个显然就没抄那个代码qwq(然后就恬不知耻地抄了另一个)

最后还是听虎哥讲听明白了,难受

开始整活

思路来自:https://blog.csdn.net/zlh_hhhh/article/details/78176629 (膜拜大佬)

首先,我们交换有三种情况,一是交换的两个数都在最优子段内,二是交换的两个数都不在最优子段内,三是一个在最优子段内一个不在

显然,前两种对我们的答案没有什么影响,我们需要处理的就是第三种。

虎哥说:“如果一道题怎么都没有思路,那么就把它当成DP来做”

于是,我们先不考虑复杂度的问题,从DP的方向来想。

我们先从左到右遍历,遍历到 \(i\),我们假设把左边的某一个数 \(a[k]\)\(a[i]\) 替换。

然后定义两个式子,1为此时新的a[i]向左延伸能得到的最大子段和,2为 \(a[i+1]\) 向右延伸能得到的最大子段和,那么显然我们的答案就是1式加2式,然后不断往下遍历取最优。

2式很简单,我们可以通过预处理直接得到,问题在于1式,这里就要用DP来考虑一下。

我们记遍历到第i位时的1式为 \(G[i]\)

那么我们的思路就是跟着定义走,枚举到 \(i\),然后从1往右枚举 \(k\),交换,然后判断此时向左延伸的最大子段和,取最优,式子写出来是:

\(G[i]=\max_{k=1}^{i-2}\big( A[k]+\max_{t=k+1}^{i-1}(\sum_{j=t}^{i-1}A[j])\big)\)

可能有神犇注意到这个式子并不完全,因为我们只枚举到了 \(i-2\),为什么不能枚举到 \(i-1\) 呢?

很显然,枚举到 \(i-2\),意味着我们向左延伸的最大子段和的长度至少为2。如果长度为1,那么此时

\(\max_{t=k+1}^{i-1}(\sum_{j=t}^{i-1}A[j])\) 显然是不执行的。

所以我们的式子只剩下: \(G[i]=\max_{k=1}^{i-1}\big( A[k])\)

\(k\) 此时等于 \(i-1\) ,所以实际上我们的选择被固定在 \(a[i-1]\) 这一个数上,所以此时长度为1的最大子段和就是 \(a[i-1]\)

而这显然不正确,如果长度大于等于2,因为考虑到子段的顺序问题,那么照上边那么做是没问题的,但我们此时长度为1,显然我们可以直接从左边找一个最大值过来换掉 \(a[i]\),而不一定是 \(a[i-1]\),所以我们需要特判一下长度为1的情况。

我们记:\(M[i-1]=\max_{k=1}^{i-1}A[k]\)

于是就有了:

\[G[i]=max\Big(\max_{k=1}^{i-2}\big( A[k]+\max_{t=k+1}^{i-1}(\sum_{j=t}^{i-1}A[j])\big),M[i-1]\Big) \]

照猫画虎,就有了:

\[G[i+1]=max\Big(\max_{k=1}^{i-1}\big( A[k]+\max_{t=k+1}^{i}(\sum_{j=t}^{i}A[j])\big),M[i]\Big) \]

我们试着改写一下:

\[G[i+1]=max\Big(\max_{k=1}^{i-1}\big( A[k]+\max_{t=k+1}^{i}(\sum_{j=t}^{i}A[j])\big),M[i]\Big ) \]

\[=max\Big(\max_{k=1}^{i-2}\big( A[k]+\max_{t=k+1}^{i-1}(\sum_{j=t}^{i-1}A[j]) )+A[i],M[i]\Big ) \]

这个改写其实是不正确的,因为我们知道\(\max_{k=1}^{i-2}\big( A[k]+\max_{t=k+1}^{i-1}(\sum_{j=t}^{i-1}A[j])\)其实是\(G[i]\)的长度至少为2的部分,在加上上边我们提出来这个 \(a[i]\),就变成了 \(G[i+1]\) 长度至少为3的部分,而 \(M[i]\) 是长度为1的部分,显然我们缺少了长度为2的部分,那么解决也很简单,直接在判断里加上就好了。

所以,最后的结果是:

\[G[i+1]=max\Big(\max_{k=1}^{i-2}\big( A[k]+\max_{t=k+1}^{i-1}(\sum_{j=t}^{i-1}A[j]) )+A[i],M[i-1]+A[i],M[i]\Big) \]

\[G[i+1]=max(G[i]+A[i],M[i-1]+a[i],M[i]) \]

递推式有了,代码就比较容易了,还有就是注意我们从左到右扫一边不能判断全部的情况,所以还要反着扫一边。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 50000 + 10;
#define ll long long
#define inf 0x3f3f3f3f

int a[maxn], maxx = -inf; 
ll sl[maxn], sr[maxn], ans = -inf;
ll G1[maxn], G2[maxn];

int main(){
	int n;
	cin >> n;
	for(int i = 1; i <= n; i++){
		scanf("%d", &a[i]);
	}
	for(int i = 1, j = n; i <= n; i++, j--){
		sl[i] = sl[i-1] + a[i];
		sr[j] = sr[j+1] + a[j]; // 保存以j为开始向右延伸的最大子段和
		if(sl[i] < 0) sl[i] = 0;
		if(sr[j] < 0) sr[j] = 0;
		if(ans < sl[i]) ans = sl[i]; // 防止存在原始的最大子段和就是最优解的情况,这是存在的,因为我们可以选择交换组合内的两个数
	}
	maxx = a[1];
	for(int i = 2; i <= n; i++){
		G1[i] = G1[i-1] + a[i-1];
		if(G1[i] < maxx) G1[i] = maxx;
		if(ans < G1[i] + sr[i+1]) ans = G1[i] + sr[i+1];
		if(maxx < a[i]) maxx = a[i];
	}
	maxx = a[n];
	for(int i = n - 1; i > 0; i--){
		G2[i] = G2[i+1] + a[i+1];
		if(G2[i] < maxx) G2[i] = maxx;
		if(ans < G2[i] + sl[i-1]) ans = G2[i] + sl[i-1];
		if(maxx < a[i]) maxx = a[i];
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2020-06-15 17:33  zfio  阅读(282)  评论(0编辑  收藏  举报