复现y总的思路:
我们需要快速得到一段区间内的乘积,并且由于没有修改操作,因此,可以使用前缀和来维护一段区间乘积是否为正
当然不需要求真的乘积,只需要知道是不是正数即可。
因此可以这样处理
if(t > 0) f[i] = f[i - 1];
else f[i] = -1 * f[i - 1];
然后我们注意到, 区间[L, R]的正负情况为f[R + 1]/f[L],因此,求正数乘积区间个数的
思路转变为求0 ~R区间内有多少个与f[R + 1]同号的区间即可
这部分也可以使用前缀和来维护,至此,一个下标内所有题目要求的区间的正负情况统计都可以在O(1)时间内完成,所以总体时间复杂度为O(n)
C++ 代码
#include <iostream>
#include <vector>
using namespace std;
using ULL = unsigned long long;
int main(){
int n;
//统计为正数的个数的前缀和
//只要求出来了正数区间个数,也就知道了负数区间个数
cin >> n;
vector<int> f(n + 1, 1),g(n + 1, 0) //f[n + 1]表示区间乘积前缀和, 正数个数前缀和
int t;
ULL ans = 0;
for(int i = 1; i <= n; ++i){
cin >> t;
if(t > 0) f[i] = f[i - 1];
else f[i] = -1 * f[i - 1];
if(f[i] > 0) g[i] = g[i - 1] + 1;
else g[i] = g[i - 1];
}
for(int i = 1; i <= n; ++i){
if(f[i] > 0) ans += g[i];
else ans += i - 1 - g[i - 1];
}
//总共有n * (n + 1) /2 个区间
cout << (n *1ULL* (n - 1))/2 + n - ans<<" " <<ans << " " <<endl;
}
再优化一下空间,事实上我们在遍历过程中,前缀和数组每次都是可以更新,并使用当前值来计算区间个数的,因此,不需要一个数组来预处理,边读入边处理即可
#include <iostream>
using namespace std;
using LL = long long;
int main(){
//S[R + 1]/S[L]表示[L, R]区间的正负情况
int n;
cin >> n;
LL ans = 0;
int pos = 0, pre = 1;
for(int i = 0; i < n; ++i){
int t; cin >> t;
if(t > 0) pre *= 1;
else pre *= -1;
if(pre > 0) ans += ++pos;
else ans += i - pos;
}
cout << 1LL*n * (n + 1)/2 - ans << " " << ans << endl;
return 0;
}