原题链接:好消息,坏消息 - 洛谷
这一题用到的技巧有:加倍变环为线、前缀和、单调队列
和B题一样,可以直接看最下面的代码部分,如果是没有思路的话,可以看看这里的实现过程 :
意思是对于n个事件,有n个不同的通报顺序,像事件1,2,3,4。它有1234 ;2341 ;3412;4123这四个可能的顺序。可以把他看作一个圆环在循环,对于这样的环状结构,我们可以用数组加倍的方式,把它变成“线”更加容易处理,像1234加倍后就是12341234,这样,每一个数加上的后面n-1个数都是上面的一种情况了。(注意最后的1234这个情况是不用管的,下面代码中有解释)
这里就是需要判断每一种情况中是否有出现负值,即对每一个i,需要判断a[i],a[i]+a[i+1],a[i]+a[i+1]+a[i+2]....., a[i]+a[i+1]+...+a[i+n-1]一共n个值是否都大于等于0,对于这样需要多次求连续区间和的情况,我们很容易就能想到用前缀和,只要预先计算一次前缀和,后续求区间和就只需要O(1)的时间就可以完成了。例:
这时我们可能就会想到要如何确定以i为起始的n个数(即一个事件顺序)要减去谁,如果是对每一个i对其进行循环像下面这样的做法:
int ans = 0;
for (int i = 1; i <= n; i++) {
bool flag = true;
for (int j = i; j <= i + n; j++) {
if (arr[j] - arr[i-1] < 0) {
flag = false;
break;
}
}
if (flag) {
ans++;
}
}
当n的值为最大1e6的时候,这样n^2的复杂度是一定超时的,所以我们需要优化它。
于是我们可以发现对于每一个i(每一个事件顺序),我们只需要管它和它后面的n-1个数就可以了,在它之前的数都是与它这个事件顺序是否合格是无关的,所以我们可以想到“先进先出”的队列对于每一个数(入队的是下标并且是用前缀和的数值来判断是否是需要的答案),我们只需要进队一次,当之前的数与当前的i无关的时候我们就抛弃它。但这样又有一个新的问题:当队列里有小于0的数,我们应该怎么放弃这个方案,之后的值又应该怎么计算等等。于是我们转换思路,我们把每一个下标放入队列并且执行n次(即第一个完整的事件顺序,此时i=n),我们开始判断队列里的每一个数和sum[i-n]相减的值是否大于等于0,但这样在时间复杂度上还是太麻烦。又因为对一个式子a-b=c来说,当减数b固定,a越小,结果c就越小,而如果当最小的值减去sum[i-n]都大于等于0,那么这个区间里的所以数减sum[i-n]都是大于等于0的。于是,我们可以想到用单调队列来存放下标,即下标所对应的值越小,它就在越前面,这样子我们就能保证这一个队列里最前面的值是这个区间里的最小值,然后再拿它和sum[i-n]相减就好了。
最后再处理队列的区间就好啦,就是下面代码里的第一个while,单调队列对于的是第二个while。
STL的deque代码实现:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstring>
#include<list>
#include<queue>
#include<deque>
using namespace std;
const int N = 2000006;
deque<int> q;
int arr[N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
scanf("%d", &arr[i]);
arr[i + n] = arr[i];//加倍变环为线
}
for (int i = 1; i < 2*n; ++i) {
arr[i] = arr[i] + arr[i-1];
}
q.push_back(0);
int ans=0;
/*
注意是小于2*n,不然会和前面的重复
如123加倍后->123123,后面的123和前面的123是同一个顺序
*/
for (int i = 1; i < 2*n; ++i) {
//掐头去尾维护i的前n个数的前缀和的最小值
while (!q.empty() && q.front() <= i-n ) {
q.pop_front();
}
while (!q.empty() && arr[i] < arr[q.back()]) {
q.pop_back();
}
q.push_back(i);
// 前缀和在这里契合题目的"按序",
//最小的前缀和减去区间外的前缀和 >0则为符合题意的答案
if (i >= n && arr[q.front()] - arr[i - n] >= 0) {
ans++;
}
}
printf("%d", ans);
return 0;
}