杭电ACM3415——Max Sum of Max-K-sub-sequence

一开始,看到这题,以为是最大连续子序列和的问题,写出了代码,提交了,WR,找了一些测试数据,结果发现这个算法并不能将所以的序列的解求出,只是满足一部分序列。

百度了一下,知道了要用单调队列来求解。

单调队列,也就是队列中必然是单调递减的或者递增的。而这题使用的是单调递增的队列。

单调队列使用的是双向队列,队尾队头都可以删除元素,只能从队尾插入元素。

比如求解一个数列{1  ,2  ,5 ,3, 4, 6}的最长的递增序列的长度。

首先,1入队,队列中有 1。 接下来2比1 大,2入队,队列为 1 ,2

接下来5比2大,5入队,队列为1, 2, 5.

接下来3比5小,删除5,3再与新的队尾比较,3比2大,将3入队,队列为1 ,2, 3。

以此类推:最后队列为 1, 2, 3, 4, 6。

而杭电3415这一题,是求一个环状的数列的最大连续序列的和,序列长度不大于K。数列的第一项和最后一项相邻。

很多人一开始的想法就是DP,可是DP并不能解决,这个我前面已经讲到了。

输入序列的长度N,和最大序列的长度为K。

因为数列是环状的,所以将数组扩大一倍,输入时,a[i] = a[i + N](1 <= i <= N);这样就解决了环状的问题。

题目是求最大连续序列的和,所以用一个数组sum来存前i个数的和(1 <= i <= 2 * N),sum[i] = sum[i - 1] + a[i].

这样问题求解的最大连续子序列和(ans)就变成了求解sum数组中在长度不超过K的情况下,ans = sum【j】- sum【i】。sum【i】为sum中相对最小的,sum【j】为相对最大的,j - i  + 1 <= K(因为有长度限制)。这样不断更新ans。这里的队列不是单调递增的,那就等价于 拿每一个sum数组的每一个与它的前面的每一个相减来更新ans,时间复杂度很大,必定超时,而使用单调队列,可以避免一些无谓的步骤。

下面是AC的代码,看注释加上上面的应该就可以理解了.

#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;

int a[200005], sum[200005];
int main()
{
	int t, N, K, i;
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d%d", &N, &K);
		int n = N;                                   //备份N
		sum[0] = 0; 
		for(i = 1; i <= N; i++)                      //输入
		{
			scanf("%d", &a[i]);
			a[N + i] = a[i];
			sum[i] = sum[i - 1] + a[i];              //求前i项的序列和(1 <= i <= N)
		}
		for(i = N + 1; i <= 2 * N; i++)
			sum[i] = sum[i - 1] + a[i];              //求前i项的序列和(N + 1 <= i <= 2 * N)
		deque<int> que;                              //定义双向队列
		int ans = -10000000;
		int start, end;
		N = N + K - 1;                               //序列长度不超过K,只需要前面的N + K - 1项就足以,
		que.clear();
		for(i = 1; i <= N; i++)                      //单调队列保持为递增的序列。
		{
			while(!que.empty() && sum[i - 1] < sum[que.back()])     //插入的数比队尾的小,删除队尾,再比较,直到比队尾大。
				que.pop_back();                                 //删除的元素的价值比插入的元素的价值小。因为要序列和最大
										//下面的ans要不断的更新,队头的元素要相对最小,使解最优。
			while(!que.empty() && i - que.front() > K)              //队列长度大于K,删除队头。
				que.pop_front();                                //保持长度不大于K,不断的更新ans
			que.push_back(i - 1);                                   //加入队尾
			if(sum[i] - sum[que.front()] > ans)                     //判断序列和是否大于ans,不断更新ans
			{
				ans = sum[i] - sum[que.front()];
				start = que.front() + 1;
				end = i;
			}
		}
		if(end > n)                                  //末尾位置超过n,减掉n。
			end -= n;
		printf("%d %d %d\n", ans, start, end);
	}
	return 0;
}

对于单调队列也是初学,如有错误,欢迎指正,互相学习。~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值