KIDx的解题报告
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3415
题意:给出一个有n个数字的环状序列(其中每个数在-1000到1000之间,且n<=100000),求一个和最大的连续子序列。(要求这个连续序列长度小于等于K)
单调队列基本模型:保持队列中元素单调递增(或递减),可以两头删除,只能从队尾插入新元素,队首q[head]为当前最优区间头,队列存的是下标。
基本思路:由于是环,补足2*n个数, 预处理出前2*n项和,枚举区间尾i, 如果当前区间尾是i,有最优区间头q[head],那么sum[i]-sum[q[head]-1]就是区间[q[head], i]的和。
为何对于当前区间尾i最优区间头是q[head]?这就是删除队列尾的功劳了:i显然可能是当前或者以后的最优区间头,所以需要插入,而q[tail-1]是队列最后一个元素,如果区间[q[tail-1], i-1]的和小于0,要让q[tail-1]作为区间头的话,必然让和更小了,所以i是更好的区间头,于是把所有劣解q[tail-1]去掉,也就是出队列,反之q[tail-1]必然比i更优,所以说队首就是当前区间的最优的区间头了
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <string> #include <cmath> #include <algorithm> #include <vector> #include <queue> #include <map> using namespace std; #define M 200005 #define inf 0x3fffffff int q[M], sum[M]; int cal (int a, int b) { return sum[b] - sum[a-1]; } int main() { int t, n, k, i; scanf("%d", &t); while (t--) { scanf ("%d%d", &n, &k); for (i = 1; i <= n; i++) scanf ("%d", sum+i); for (i = n+1; i <= 2*n; i++) sum[i] = sum[i-n]; for (i = 2; i <= 2*n; i++) //预处理前2*n项和(其实n+k即可) sum[i] += sum[i-1]; int head = 0, tail = 0, maxs = -inf, a, b; for (i = 1; i <= 2*n; i++) //枚举区间尾i { //把劣解出队列 while (tail > head && cal(q[tail-1], i-1) < 0) --tail; q[tail++] = i; //当前最优区间头q[head],计算区间[q[head], i]的和 int tp = cal(q[head], i); if (tp > maxs) { maxs = tp; a = q[head]; b = i; } //超出长度,q[head]已经不能为下一个区间尾所用就出队列 while (tail > head && i - q[head] + 1 >= k) ++head; } if (b > n) b -= n; //b可能大于n而不满足题意 printf ("%d %d %d\n", maxs, a, b); } return 0; }