洛谷P2629 好消息,坏消息
题目大意:
有n个数,k属于[1,n],对于每个k是从k加到n再从1加到k-1,问有几个k可以使得在加的过程中不出现负数。
思路:
[k,n]再是[1,k-1]的累加,那就是一个环的问题,用断环为链的方法。
至此,任意k时的累加过程就变成了k后面(包括k)n个数的累加过程(是不是很妙!)。
而想到累加就不得不提到前缀和。再做一个前缀和数组:
至此,任意区间求和的过程就变成了o(1)的减法,即p[k+n] - p[k - 1] 。
到这有人要问了,诶,前缀和是直接算出来区间和,那区间的求和过程是未知的你咋知道求和过程中有没有出现过负数嘞?
我们再观察一下题目要求的结构,k是[1,n]一个一个累加的,而区间长度一直是n没有变,有没有感觉眼熟?这就是单调双端队列。单调双端队列解决的是滑动区间最值问题。这个题和最值乍一看好像没什么关系,但是在[k,k+n]的求和过程被我们用p[k+n] - p[k - 1] 代替之后,我们是不是可以对前缀和数组p维护一个单调双端队列,来做每一段[k,k+n]的前缀和的最小值。因为区间中最小的前缀和减端点如果比0小就说明过程中出现了累加小于零的情况!!
(真的是很巧妙的想法啊!!!!!)
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll ; const ll N = 2 * 1e6 + 50 ; ll n , ans ; ll a[N] ; deque<ll>q ; int main(){ cin>>n; for(ll i = 1 ; i<= n ;i ++ )cin>>a[i] ; for(ll i = n + 1 ; i < 2 * n ; i ++ )a[i] = a[i - n] ; for(ll i = 1 ; i < 2 * n ; i ++ )a[i] += a[i - 1] ; //for(ll i = 1 ; i < 2 * n ; i ++ )cout<<a[i]<<" ";cout<<endl; for(ll i = 1 ; i < 2 * n ; i ++ ){ while(q.size() && a[q.back()] >= a[i])q.pop_back() ;//做一个单调队列 q.push_back(i) ; //for(auto x : q)cout<<a[x]<<" ";cout<<endl; if(i >= n ){ while(q.size() && q.front() <= i - n )q.pop_front() ;//要用的时候弹出不不符合要求的元素 //cout<<"k="<<i - n + 1<<" sum="<<a[q.front()] - a[i - n]<<"\n"; if(a[q.front()] >= a[i - n])ans ++ ; } } cout<<ans ; }