题目描述
给定一个长度为 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);
}
}
欢迎留言点赞
嗷嗷嗷~