一名苦逼的OIer,想成为ACMer

Iowa_Battleship

洛谷1121 环状最大两段子段和

原题链接

乍看完全没啥想法,实际上很简单。
首先预备知识:最大子段和的做法。
对于环状最大两段子段和,实际上只有两种情况(设\(0\)表示不取,\(\_\)表示取):

  1. \(0000\_\_\_\_0000\_\_\_\_0000\),即没有利用环状这个条件,直接在原序列里取。
  2. \(\_\_0000\_\_\_\_0000\_\_\),即取的子段中有一段是利用环状条件的。

\(1\)种情况很简单,就是按从前往后和从后往前跑两遍普通的最大子段和,再分割取两段和的最大值。
而第\(2\)种情况正着做比较困难,那么考虑反着做,发现不取的子段实际上是第\(1\)种情况,只不过这两段是最小子段和,因此我们可以找出最小子段和,用总和减去后就是第\(2\)种情况了。
而求最小子段和可以改变\(DP\)方法,但为了打起来简单,可以直接将序列里的每个数都变为其相反数,然后直接跑最大子段和即可。
需要注意的是,取相反数的做法在该序列只有一个正数的情况会出错,实际上这种情况的答案就是第\(1\)种得到的答案,因此直接特判掉就好。
而若第\(2\)种情况返回的值就是总和的话,就说明答案肯定是第\(1\)种情况,直接特判掉即可。

#include<cstdio>
#include<cstring>
using namespace std;
const int N = 2e5 + 10;
int a[N], f[N], g[N], n;
inline int re()
{
	int x = 0;
	char c = getchar();
	bool p = 0;
	for (; c < '0' || c > '9'; c = getchar())
		p |= c == '-';
	for (; c >= '0' && c <= '9'; c = getchar())
		x = x * 10 + c - '0';
	return p ? -x : x;
}
inline int maxn(int x, int y) { return x > y ? x : y; }
inline int DP()
{
	int i, ma = -1e9;
	for (i = 1; i <= n; i++)
		f[i] = maxn(f[i - 1], 0) + a[i];
	for (i = 1; i <= n; i++)
		f[i] = maxn(f[i - 1], f[i]);
	for (i = n; i; i--)
		g[i] = maxn(g[i + 1], 0) + a[i];
	for (i = n; i; i--)
		g[i] = maxn(g[i + 1], g[i]);
	for (i = 1; i < n; i++)
		ma = maxn(ma, f[i] + g[i + 1]);
	return ma;
}
int main()
{
	int i, s = 0, k = 0, ma_1, ma_2;
	n = re();
	for (i = 1; i <= n; i++)
		a[i] = re(), s += a[i], k += a[i] > 0;
	memset(f, 250, sizeof(f));
	memset(g, 250, sizeof(g));
	if (k < 2)
		return printf("%d", DP()), 0;
	ma_1 = DP();
	for (i = 1; i <= n; i++)
		a[i] = -a[i];
	ma_2 = s + DP();
	printf("%d", maxn(ma_1, ma_2 ? ma_2 : -1e9));
	return 0;
}

posted on 2019-03-25 21:05  Iowa_Battleship  阅读(318)  评论(1编辑  收藏  举报

导航