一开始,看到这题,以为是最大连续子序列和的问题,写出了代码,提交了,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; }
对于单调队列也是初学,如有错误,欢迎指正,互相学习。~~