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;
}