题目描述
难度分:1400
输入n(1≤n≤2×105)和长为n的数组a(−109≤a[i]≤109,a[i]≠0)。
输出两个数:
- 有多少个非空连续子数组,乘积是负数?
- 有多少个非空连续子数组,乘积是正数?
输入样例1
5
5 -3 3 -1 1
输出样例1
8 7
输入样例2
10
4 2 -4 3 1 2 -4 3 2 3
输出样例2
28 27
输入样例3
5
-1 -2 -3 -4 -5
输出样例3
9 6
算法
前缀和
本题中没有a[i]=0的情况,还是很容易分析的。只要子数组中负数的个数是奇数,其乘积结果就是负数。开辟一个数组cnt1×2,其中cnt[0]表示数组a的前缀[1,i)中负数出现次数为偶数的次数,cnt[1]表示数组a的前缀[1,i)中负数出现次数为奇数的次数。
然后从左往右遍历i∈[1,n],当遍历到a[i]时,维护一个前缀[1,i]中负数的个数presum:
- 如果presum为奇数,就考察前面所有位置j∈i中,前缀[1,j]中负数个数为偶数的次数cnt[0],满足这个条件的所有j,都会有子数组(j,i]中有奇数个负数,把cnt[0]累加到答案上即可。而由于此时presum是奇数,所以cnt[1]自增。
- 同理分析presum为偶数的情况就行了。
按照以上的算法流程就能计算出乘积结果为负数的子数组数量,而a总共的子数组数量为n×(n+1)2,用它减去乘积结果为负数的子数组数量就能得到乘积结果为偶数的子数组数量。
复杂度分析
时间复杂度
只遍历了一遍数组a,时间复杂度为O(n)。
空间复杂度
尽管开辟了一个数组用于记录前缀中负数数量偶数或奇数次的频数,但这个数组的大小恒定为2,与数据量n没有关系,所以总的变量数也就是有限几个,额外空间复杂度为O(1)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int main() {
int n;
scanf("%d", &n);
int cnt[2] = {0};
cnt[0] = 1;
int presum = 0;
LL ans = 0;
for(int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
if(x < 0) presum++;
if(presum&1) {
ans += cnt[0];
cnt[1]++;
}else {
ans += cnt[1];
cnt[0]++;
}
}
printf("%lld %lld\n", ans, (LL)n*(n + 1)/2 - ans);
return 0;
}