关于最大子段和及其变式

建议看这篇博客

本篇文章记录了笔者的一些理解,有点方面并没有写的很完善,请见谅。

最大子段和

\(f_i\) 为以 \(i\) 为结尾的最大子段和。

考虑一个 \(i\),若前面的最大子段加上自己不如自己作为单独一个头的话,那么就取自己,即为

\[f_i=\max(f_{i-1}+a_i,a_i)=\max(f_{i-1},0)+a_i \]

2023/10/20 upd:注意:这里所有的 \(a_i\) 都应为非负数。
#include <bits/stdc++.h>
using namespace std;

const int N = 1e6+5;
int n, ans = -1e9, a[N], f[N];

int main() {
	ios::sync_with_stdio(0);
	cin >> n;
	for (int i = 1; i <= n; ++i) cin >> a[i];
	f[1] = a[1];
	for (int i = 2; i <= n; ++i) f[i] = max(f[i-1]+a[i], a[i]);
	for (int i = 2; i <= n; ++i) f[i] = max(f[i-1], f[i]);
	cout << f[n];
	return 0;
}

例题:P1115

环状最大子段和

可以考虑破环成链,前提是得限制子段的长度,可以选择转移时记录当前子段的长度,较为麻烦。最大子段和等价于总和减去最小补集,因此我们希望尽可能地向这方面转化问题。考虑环状最大子段的位置。若环状最大子段没有过端点,那么正常求一遍最大子段和。否则,补集在区间内,我们只需求最小子段和即可。

注意:若题目要求子段不为空,那么最小子段不能覆盖到整个区间,那么只需求 \([1,n-1]\)\([2,n]\) 的最小子段和,并对以上情况取最大值即可。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6+5;
int n, sum, ans = -1e9, a[N], f[N];

int getmax(int l, int r) {
	f[l] = a[l];
	for (int i = l+1; i <= r; ++i) f[i] = max(f[i-1]+a[i], a[i]);
	for (int i = l+1; i <= r; ++i) f[i] = max(f[i-1], f[i]);
	return f[r];
}

int getmin(int l, int r) {
	f[l] = a[l];
	for (int i = l+1; i <= r; ++i) f[i] = min(f[i-1]+a[i], a[i]);
	for (int i = l+1; i <= r; ++i) f[i] = min(f[i-1], f[i]);
	return f[r];
}

int main() {
	ios::sync_with_stdio(0);
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		sum += a[i];
	}
	ans = max(ans, getmax(1, n));
	ans = max(ans, sum-getmin(1, n-1));
	ans = max(ans, sum-getmin(2, n));
	cout << ans;
	return 0;
}

最大两段子段和

仿造最大子段和的分析,考虑两个子段之间的一个断点 \(i\),那么答案为 \(i\) 前面最大子段和加上 \(i\) 后面的最大子段和,且这两个子段之间没有相交部分,也没有越过 \(i\)。因此,我们定义 \(f_i\) 为以 \(i\) 为右端点的最大子段和,\(g_i\) 为以 \(i\) 为左端点的最大子段和,\(f'_ i\)\(\max_{j=1}^i\{f_j\}\)\(g'_ i\)\(\max_{j=i}^n\{g_j\}\)。那么答案为 \(\max_{i=2}^{n-1}\{f'_ {i-1}+g'_ {i+1}\}\)

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e6+5;
int n, ans = -1e18, a[N], f[N], g[N];

signed main() {
	ios::sync_with_stdio(0);
	cin >> n;
	for (int i = 1; i <= n; ++i) cin >> a[i];
	f[1] = a[1], g[n] = a[n];
	for (int i = 2; i <= n; ++i) f[i] = max(f[i-1]+a[i], a[i]);
	for (int i = 2; i <= n; ++i) f[i] = max(f[i-1], f[i]);
	for (int i = n-1; i; --i) g[i] = max(g[i+1]+a[i], a[i]);
	for (int i = n-1; i; --i) g[i] = max(g[i+1], g[i]);
	for (int i = 2; i < n; ++i) ans = max(ans, f[i-1]+g[i+1]);
	cout << ans;
	return 0;
}

例题:P2642

环状最大两段子段和

相信读者会了环状最大子段和 and 最大两段子段和后这个就能自己推出来了。只需两个结合起来,求最大两段子段和 and 最小两段子段和。

注意:若题目要求子段非空,两段子段和之间是不能相邻的。还需注意下面的例题中环状最大两子段是可以相邻的,那么补集可以不取。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6+5;
int n, sum, ans, a[N], f[N], g[N];

int getmax(int l, int r) {
	int res = -1e9;
	f[l] = a[l], g[r] = a[r];
	for (int i = l+1; i <= r; ++i) f[i] = max(f[i-1]+a[i], a[i]);
	for (int i = l+1; i <= r; ++i) f[i] = max(f[i-1], f[i]);
	for (int i = r-1; i >= l; --i) g[i] = max(g[i+1]+a[i], a[i]);
	for (int i = r-1; i >= l; --i) g[i] = max(g[i+1], g[i]);
	for (int i = l; i < r; ++i) res = max(res, f[i]+g[i+1]);
	return res;
}

int getmin(int l, int r) {
	int res = 1e9;
	f[l] = a[l], g[r] = a[r];
	for (int i = l+1; i <= r; ++i) f[i] = min(f[i-1]+a[i], min(a[i], 0));
	for (int i = l+1; i <= r; ++i) f[i] = min(f[i-1], f[i]);
	for (int i = r-1; i >= l; --i) g[i] = min(g[i+1]+a[i], min(a[i], 0));
	for (int i = r-1; i >= l; --i) g[i] = min(g[i+1], g[i]);
	for (int i = l+1; i < r; ++i) res = min(res, f[i-1]+g[i+1]);
	return res;
}

int main() {
	ios::sync_with_stdio(0);
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		sum += a[i];
	}
	ans = getmax(1, n);
	ans = max(ans, sum-getmin(1, n-1));
	ans = max(ans, sum-getmin(2, n));
	cout << ans;
	return 0;
}

例题:P1121

posted @ 2023-12-16 11:43  123wwm  阅读(18)  评论(0编辑  收藏  举报