Week13 作业 E - TT 的神秘任务3 HDU - 3415

题目描述:

给定一个环,A[1], A[2], A[3], … , A[n],其中 A[1] 的左边是 A[n]。要求从环上找出一段长度不超过 K 的连续序列,使其和最大。

思路:

  • 定义状态:F[i]表示以A[i]为结尾的最大值(A[i]必选)

  • 状态表达式:F[i]=sum[i]-min{sum[j]},其中i-K<j<i,sum表示前缀和,如此定义状态,就没有状态转移表达式,因为F[i]不能由F[i-1]得到,因为不知道F[i-1]的序列长度(信息不足)

  • 边界条件:不需要,因为不需要状态转移

  • 答案:max{F[i]},可以进一步发现,F数组没有必要存储,边计算边更新答案即可

  • 更进一步,发现A数组也没必要存储,因为前缀和数组可以在输入时计算出来

  • 处理环形序列的方法:和石子排列一样的思想,把环变成线,添加冗余数据

使用单调队列优化状态表达式

  • min{sum[j]}如果每次搜索都去查找,则每次搜索的复杂度是O(K),可以发现,我们的sum[j]像滑动窗口一样在流动,可以使用一个递增的单调队列,队头每次都存储最小值

  • 第i次迭代:取队头元素,计算F[i](更新答案),弹出队列中过时元素,把sum[i]放进队列

代码:

//进一步发现,a[i]的存储没有必要
#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include <iostream>
#include <deque>
#include <cstring>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 5;
int a[2 * MAXN];
ll sum[2 * MAXN];
int main()
{
	int T; cin >> T;
	int N, K;
	while (T--)
	{
		memset(sum, 0, sizeof(sum));
		cin >> N >> K;
		for (int i = 1; i <= N; i++)
			scanf("%d", a + i);
		for (int i = 1; i <= N-1; i++)
			a[N + i] = a[i];
		N = N + K - 1;
		//calc prefixSum
		for (int i = 1; i <= N; i++)
			sum[i] = sum[i - 1] + a[i];
		//process
		ll ans = -1e10, l = 0, r = 0;
		deque<int> q;
		q.push_back(0);
		for (int i = 1; i <= N; ++i)
		{
			//把滑动窗口维护在当前元素的前面,而不是包括当前元素
			//因为迭代到i时,序列是必须选a[i]的,但是如果把滑动窗口包括了i,则可能出现sum[i]-sum[i]=0,没包括任何元素,很显然不符合预期
			if (sum[i] - sum[q.front()] > ans)
			{
				ans = sum[i] - sum[q.front()];
				l = q.front() + 1LL;
				r = i;
			}
			//维护递增队列,队头元素总是最小的;这里不是严格递增,能保证字典序
			while (!q.empty() && sum[q.back()] > sum[i])
				q.pop_back();
			//丢弃过期元素
			while (!q.empty() && q.front() <= i - K)	
				q.pop_front();
			q.push_back(i);
		}
		N = N + 1 - K;
		if (r > N) r = r - N;
		cout << ans << ' ' << l << ' ' << r << endl;
	}
	return 0;
}
posted @ 2020-06-11 17:02  菜鸡今天学习了吗  阅读(162)  评论(0编辑  收藏  举报