CF585_乘积数量

题目链接: CF585. 乘积数量

题目描述

给定一个长度为 n 且不包含 0 的整数序列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an

请你计算以下两值:

  1. 使得 a l × a l + 1 × … × a r a_l×a_{l+1}×…×a_r al×al+1××ar 为负的索引对 (l,r)且(l≤r)的数量。
  2. 使得 a l × a l + 1 × … × a r a_l×a_{l+1}×…×a_r al×al+1××ar 为正的索引对 (l,r)且(l≤r)的数量。
输入格式

第一行一个整数 n。

第二行包含 n 个整数 a 1 , … , a n a1,…,an a1,,an.

输出格式

共一行,输出单个空格隔开的两个整数,分别表示负的索引对数和正的索引对数。

数据范围

1 ≤ n ≤ 2 × 1 0 5 1 ≤ n ≤ 2×10^5 1n2×105
− 1 0 9 ≤ a i ≤ 1 0 9 , a i ≠ 0 −10^9≤ a_i ≤ 10^9,a_i≠0 109ai109,ai=0

样例
输入样例1:
5
5 -3 3 -1 1
输出样例1:
8 7
    
输入样例210
4 2 -4 3 1 2 -4 3 2 3
输出样例228 27
    
输入样例35
-1 -2 -3 -4 -5
输出样例3:
9 6

算法1

思路分析

首先分析题目我们看到, 需要计算 a l × a l + 1 × … × a r a_l×a_{l+1}×…×a_r al×al+1××ar这段区间乘积的正负情况, 快速计算出一段区间内的运算结果, 我们可以很快地想到前缀和的思想, 这里当然并不是算和, 但是我们同样可以用这个思想预处理出一段区间的乘积是多少, 这样我们就可以用 O ( 1 ) O(1) O(1) 的时间快速得到一段区间的乘积, 而且由于只需要判断区间的乘积情况, 故我们并不需要真的存下具体的 a i a_i ai,只需要存下其正负号即可. 这样处理之后得到一个数组s, s[i] 的含义为 a 1 × a 2 × a 3 . . . . × a i a_1 \times a_2 \times a_3 .... \times ai a1×a2×a3....×ai 的正负情况, 如果我们想知道一段区间的正负情况, 例如想知道区间(l, r) 乘积的正负情况, 即得到 a l × a l + 1 × … × a r a_l×a_{l+1}×…×a_r al×al+1××ar 的正负情况, 用我们的s数组表示则为 s [ r ] s [ l − 1 ] \frac{s[r]}{s[l - 1]} s[l1]s[r] , 其中 1 ≤ \le l ≤ \le r, 那么以 a r a_r ar为右端点的区间个数中, 其区间乘积结果的正负取决于 s [ l − 1 ] s[l - 1] s[l1] s [ r ] s[r] s[r] 的正负情况, 若两者符号同号, 则这段区间的乘积结果为正数, 若两者符号相反, 则这段区间的乘积结果为负数. 故以 a r a_r ar为右端点且乘积为正数的区间的个数等于s[l - 1] (1 ≤ \le l ≤ \le r) 中, 即s[ 0 0 0 ~ r − 1 r - 1 r1]中和s[r]符号相同的个数, 同理乘积为负数的区间个数等于与s[r]符号相反的元素个数. 故我们只要遍历所有的 a i a_i ai(从 a 1 a_1 a1 ~ a n a_n an),计算出以 a i a_i ai为右端点的情况,即可得到所有的区间情况,故接下来具体看代码实现, 如何实现上面的过程.

C++ 代码1
#include<iostream>
using namespace std;
constexpr int N = 2 * 1e+5 + 10;
int s[N];              // 前缀和数组

auto main() -> int
{
    ios::sync_with_stdio(false); 
    int n; cin >> n;    
    s[0] = 1;         // 前缀s[0] 预处理为1 
    
    //res1 表示乘积为正数的区间个数, res2 表示乘积为负数的区间个数
    // j 表示目前s[0 ~ i - 1]中符号为正的个数, k 表示目前s[0 ~ i - 1]中符号为负的个数
    // 由于s[0] = 1, 故目前 j = 1, k = 0, 由于数据会爆int, 故这里用long long
    long long res1 = 0, res2 = 0, j = 1, k = 0;   
    
    for(int i = 1; i <= n; ++i)    // 从a1 ~ an 遍历, 边输入边处理
    {
        int flag = 1;              
        int a; cin >> a; 
        if(a < 0) flag = -1;      // 若本次的遍历的值为负, 则乘积结果为s[i - 1] 的相反数
        s[i] = s[i - 1] * flag;   
        
        // 若s[i]为正数, 则乘积为正的区间个数为s[0 ~ i - 1]中符号为正的个数,即为j目前的个数. 
        // 乘积为负的区间个数为s[0 ~ i - 1]中符号为负的个数, 即为k, 最后由于s[i]是正数,故++j,负数情况同样分析
        if(s[i] > 0) res1 += j, res2 += k, ++j;   
        else res1 += k, res2 += j, ++k; 
    }
    
    cout << res2 << " " << res1 << endl; 
    return 0;
}
C++ 代码2 改进

由于我们实际计算的过程中并不需要用一个数组存下之前的所有s[ i i i ~ n − 1 n - 1 n1] 的情况, 我们计算一个s[i]的时候只需要知道s[ i − 1 i - 1 i1]的正负情况即可, 故这里我们可以不需要这个数组, 直接用一个pre变量表示s[i - 1]即可. 剩下的情况分析,与上面相同, 由于我们这个算法只需遍历一遍我们的序列即可, 故其时间复杂度为 O ( n ) O(n) O(n).

#include<iostream>
using namespace std;
constexpr int N = 2 * 1e+5 + 10;

auto main() -> int
{
    ios::sync_with_stdio(false); 
    int n; cin >> n;    
    
    long long res1 = 0, res2 = 0, j = 1, k = 0, pre = 1;  
    
    for(int i = 1; i <= n; ++i)
    {
        int a; cin >> a; 
        if(a < 0) pre *= -1; 
        
        if(pre > 0) res1 += j, res2 += k, ++j;
        else res1 += k, res2 += j, ++k; 
    }
    
    cout << res2 << " " << res1 << endl; 
    return 0;
}
时间复杂度 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值