乘积数量(索引)

题目描述
给定一个长度为 n 且不包含 0 的整数序列 a1,a2,…,an。

请你计算以下两值:

使得 al×al+1×…×ar 为负的索引对 (l,r)(l≤r) 的数量。
使得 al×al+1×…×ar 为正的索引对 (l,r)(l≤r) 的数量。
输入格式
第一行一个整数 n。

第二行包含 n 个整数 a1,…,an。

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

数据范围
1≤n≤2×105,
−109≤ai≤109,ai≠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

思路一: DP
dpi,0 表示前ii个乘积为正的区间数量,dpi,1 表示前ii个乘积为负的区间数量
属性:Count
状态计算(集合划分):
第i个数为正数,那么前i−1个正区间都可以乘上这个数变成正区间第i个数为正数,
那么前i−1个正区间都可以乘上这个数变成正区间
另外自己也单独算一个正区间,所以第i个正区间的个数=前i−1个正区间的个数+1另外自己也单独算一个正区间,
所以第i个正区间的个数=前i−1个正区间的个数+1
dpi,0=dpi−1,0+1
前i−1个负区间要乘上这个正数才能变成负区间前i−1个负区间要乘上这个正数才能变成负区间
所以第i个负区间的个数=前i−1个负区间的个数,即:所以第i个负区间的个数=前i−1个负区间的个数,即:
dpi,1=dpi−1,1

时间复杂度 O(n)
参考文献
C++ 代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e6+10;
int f[N][2];
int n;
LL ans,res;
int main()
{
    cin>>n;
    for (int i = 1; i <= n; i ++ )
    {
        int x;cin>>x;
        if(x>0)
        {
            f[i][0]=f[i-1][0]+1;
            f[i][1]=f[i-1][1];
        }
        else
        {
            f[i][1]=f[i-1][0]+1;
            f[i][0]=f[i-1][1];
        }
        res+=f[i][1];
        ans+=f[i][0];
    }
  //  cout<<f[n][1]<<' '<<f[n][0];
    cout<<res<<' '<<ans;
    return 0;
}

Java代码

import java.util.*;
import java.io.*;

public class Main{
    static int N = 1000010;
    static int[][] f = new int[N][2];
    static int n;
    public static void main(String[] args) throws IOException{
        Scanner cin = new Scanner(System.in);
        n=cin.nextInt();
        long ans=0,res=0;
        for(int i=1;i<=n;i++)
        {
            int x=cin.nextInt();
            if(x>0)
            {
            f[i][0]=f[i-1][0]+1;
            f[i][1]=f[i-1][1];
            }
            else
            {
            f[i][1]=f[i-1][0]+1;
            f[i][0]=f[i-1][1];
            }
            ans+=f[i][1];
            res+=f[i][0];
         }
        System.out.print(ans + " " + res);
    }
}

GO代码 TLE(第七个点过不去)
 

package main 


import "fmt"
const N=1000010
var f[N][2] float64
var n int 
var res,ans float64

func main() {
    fmt.Scanf("%d",&n)
    for i := 1; i <= n; i ++ {
        var x int
        fmt.Scanf("%d",&x)
        if x > 0 {
            f[i][0] = f[i-1][0] + 1
            f[i][1] = f[i-1][1]
        }else{
            f[i][1]=f[i-1][0]+1;
            f[i][0]=f[i-1][1];
        }
        res += f[i][1]
        ans += f[i][0]
    }
    fmt.Println(res,ans)
}

思路二:
前缀和
顾名思义,我们可以用一个前缀积数组,来判断一个区间 [sl,sr] 的乘积的正负数

同样我们不需要完全模拟出前缀的乘积,也只需维护正负号的数量关系,原因同之前一样

于是我们可以规定区间:

[1,si]=1 表示该段前缀区间的乘积为正数

[1,si]=−1 表示该段前缀区间的乘积为负数

然后我们就可以枚举区间的右端点 sr,寻找合法的左端点了 sl
**`区间积为负数⇔[sl,sr]<0⇔sr÷sl−1=−1

区间积为正数⇔[sl,sr]>0⇔sr÷sl
因此,我们从前往后枚举右端点的时候,还需要额外记录前缀积为正数和负数的个数
,也就是对于当前右端点来说,合法的左端点个数。

把这些区间个数相加,就是最终答案了
当然,
连续区间个数是(n+1)*n/2 (以每个点为起点的区间个数是一个等差数列,利用公式求和可以求出连续区间总数)
那我们减一下就可以求出负的索引对了。
这里就不用上面那个公式

时间复杂度 O(n)
参考文献
C++ 代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int n,cur,n1,n2;
LL ans,res;
int main()
{
    cin>>n;
    n1=cur=1;
    for (int i = 1; i <= n; i ++ )
    {
        int x;cin>>x;
        if(x<0)cur*=-1;
        if(cur>0)ans+=n1,res+=n2,n1++;
        else ans+=n2,res+=n1,n2++;
    }
  //  cout<<f[n][1]<<' '<<f[n][0];
    cout<<res<<' '<<ans;
    return 0;
}

GO代码

package main 


import (
    "fmt"
    )

var n, m int 


func main() {
    fmt.Scanf("%d",&n)
    var sign int = 1
    var pos int = 1 
    var neg, res_neg, res_pos int  


    for i := 0 ; i < n ; i ++ {
        fmt.Scanf("%d",&m)
        if m < 0 {
            sign *= - 1
        }
        if sign > 0 {
            res_pos += pos
            res_neg += neg
            pos ++
        }else {
            res_pos += neg
            res_neg += pos
            neg ++

        }
    }
    fmt.Println( res_neg,res_pos)
}

Java代码

import java.util.*;
public class Main{
    public static void main(String[]args){
        Scanner sc=new Scanner(System.in);
        long rp=0,rn=0;
        int sp=1,sn=0,s=1;
        int n=sc.nextInt();
        for(int i=0;i<n;i++){
            int x=sc.nextInt();
            if(x<0) s*=-1;
            if(s>0){
                rp+=sp;
                rn+=sn;
                sp++;
            }  
            else{
                rp+=sn;
                rn+=sp;
                sn++;
            }
        }
        System.out.printf("%d %d",rn,rp);
    }
}

欢迎留言点赞

嗷嗷嗷~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值