来一发楼下兄弟没想到的单调队列做法。
这道题其实是预处理难想,单调队列还是很简单的
我是通过找规律做出来的
首先这是样例。
-3 5 1 2
然后这是样例和样例的前缀和
-3 5 1 2
-3 2 3 5
然后这是样例和所有倒叙的可能前缀和(显然这不就是破环为列嘛)
-3 5 1 2
-3 2 3 5
5 6 8 5
1 3 0 5
2-1 4 5
然后我们发现,这个方阵除第一行,最后一列以外
每个数都等于右上角的数减去上一行第一个数。
现在是见证奇迹的时刻!如果我们认为最后一列也满足这个规律,而不是简单的等于上一个数呢?
那显然需要构造一些数了,构造后的方阵如下
-3 5 1 2
-3 2 3 5 2 7 8
5 6 8 5 1011
1 3 0 5 6
2 -14 5
然后我们发现,如果在第一行取一个长度为n的区间
那么d[t]-d[t+i](t为区间的起点)就是第t+1行第i-1个数的值!
(因为第n个永远是正的,不管它)
也就是说,每一个长度为n且起点为最小值的区间,就对应一个方案。
最小值好办,单调队列O(n)扫一遍搞定。
那么如何生成第一行?
根据我们构造的原理(或者找规律)有:
d[i]=d[i-n]+d[n]
然后就可以做啦~
上代码~
#include<stdio.h> int n; int sum[2000020]; struct data { int n;int v; }q[2000020];//数组是两倍!两倍! int head=1;int tail=0;//手写队列部分~ inline void push(data x) { //printf("push(%d,%d)\n",x.v,x.n); q[++tail]=x;return; } inline void pop() { if(head<=tail)head++; return; } inline void pot() { if(head<=tail)tail--; return; } inline bool empty() { if(head>tail) { head=1;tail=0; return true; } else return false; } int res; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&sum[i]); sum[i]+=sum[i-1];//处理前缀和 } for(int i=n+1;i<2*n;i++) { sum[i]=sum[i-n]+sum[n];//处理人造前缀和 } for(int i=1;i<2*n;i++)//单调队列模板 { if(!empty()) { while(1) { //printf("tail.v=%d\n",q[tail].v); if(q[tail].v>sum[i]) { pot();if(empty())break; } else if(q[tail].v<=sum[i]) { data p;p.v=sum[i]; p.n=i; push(p);break; } } } if(empty()) { data p;p.v=sum[i];p.n=i; push(p); } /*printf("sum[%d]=%d\n",i,sum[i]); for(int j=head;j<=tail;j++) { printf("%d ",q[j].v); }printf("\n"); for(int j=head;j<=tail;j++) { printf("%d ",q[j].n); }printf("\n");*/ if(q[tail].n-q[head].n==n-1)//如果成功构造出长度为n的区间,答案+1 { res++;pop(); } } printf("%d",res); return 0;//拜拜程序~ }
原文:http://www.cnblogs.com/SHADOWICENCXIX/p/7705173.html