初学单调队列,纠结了好久这个题,一直没理解原理是怎么回事,网上搜解题报告一股脑看下去还是没看个明白、、、、、终于慢慢摸索出来了
首先理解这个题的意思:一个数列,可以将最前一个数放在最后,也就是这是个数列是环状的,比说1,-2,3这个数列可以写成-2,3,1,和3,1,-2两种循环方式
题目要求统计这个数列所有循环中每一个都满足前i(1<= i <=n)个数之和为非负数(>=0)的i的个数。
思路:从1到n枚举i,求出每一个循环状态前i个数之和min,若min<0则 i 不满足条件,否则ans累加计数。然后问题就是如果依次枚举循环求和肯定超时,如何优化?
大神有用线段树有用其他各种巧办法、、、这里写下单调队列希望比那些单调队列报告更好理解。
先借个图:http://www.2cto.com/kf/201311/255850.html
循环的状况一般都把数列再往后复制一遍(图中黄色)
sum[ i ]=a[1]+a[2]+...+a[i];
思路不变。图中红色方框从2向右就开始移动,每次都对应一个循环状态,单调队列维护红框中的sum[ ](递增),这是取单调队列q的第一个值q[ 0 ](红框中sum[ ]的最小值),
用sum[ q[0] ] 去减红框左边外面第一个数也就是sum[ 1 ],即sum[ q[0] ]-sum[ 1 ]=t,若 t >=0 说明所有循环状态中的前 1 个数的和都>=0,则ans可以计数。为什么呢。。。
想一下,若红框中有A[ i ] < 0 那么sum[ i ]相对sum[ i-1 ]必定是减少的,相对方框左边外面第一个sum[ ]肯定也是减少的,这样的一个差必为负,而为什么要取红框左外第一个数,因为我们红框右移同时左边外面的数列递增到k个,我们可以通过取红框内最小值去减这个数一次性判断前k个数之和是否>=0
当红框再向右移时,有 sum[ q[ 0 ] ] - sum[ 2 ] = t,同理,t >= 0说明所有循环状态中前2个数的和都 >=0,否则存在负数
q始终维护红框内的最小sum[ ],每次移动判断 sum[ q[ 0 ] ] - sum[ i - n ]的值正负状况(i为红框内最右一个数的下标)
#include<stdio.h>
#include<string.h>
#define MAXN 1000005
int q[MAXN*2];//单调队列
int sum[MAXN*2];
int main()
{
int n,i;
while(scanf("%d",&n)!=EOF)
{
if(n==0) break;
for(i=1;i<=n;i++)
{
scanf("%d",&sum[i]);
sum[i+n]=sum[i];
}
for(i=1;i<=(n<<1);i++)
sum[i]+=sum[i-1];
int head=0,rear=0;
int ans=0;
for(i=2;i<=(n<<1);i++)
{
while(head<rear && sum[q[rear-1]]>=sum[i])
--rear;
q[rear++]=i;
if(i>n&&sum[q[head]]-sum[i-n]>=0)
++ans;
while(q[rear-1]-q[head]+1>n)
++head;
}
printf("%d\n",ans);
}
return 0;
}