1. 题目来源
链接:3574. 乘积数量
2. 题目解析
所有方案满足等差数列求和公式,故所有方案数为 n(1+n)/2
。会爆 int
注意开 long long
。
dp
解法:
f[i][0]
表示以i
结尾的正数索引的方案数f[i][1]
表示以i
结尾的负数索引的方案数- 答案需要累加
i
:1~n
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5+5;
int n;
LL f[N][2];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) {
int x;
scanf("%d", &x);
if (x > 0) {
f[i][0] += f[i - 1][0] + 1;
f[i][1] += f[i - 1][1];
} else {
f[i][0] += f[i - 1][1];
f[i][1] += f[i - 1][0] + 1;
}
}
LL a = 0, b = 0;
for (int i = 1; i <= n; i ++ )
a += f[i][1], b += f[i][0];
printf("%lld %lld\n", a, b);
return 0;
}
前缀和解法:
这里理解为前缀乘积更加容易。
前缀和 s[i]
表示前 i
个数的乘积,注意 s[0] = 1
。
则 s[r] / s[l - 1]
就是区间 a[l ~ r]
的乘积。我们只关心正负,故仅需要存前缀和数组 s[i]
的正负即可。
针对一个 s[i]
以 i
结尾的区间,我们只需要知道 s[i]
的正负,针对任一一个区间 [j, i]
那么有区间乘积为 s[i]/s[j-1]
。s[i]
的正负确定,只需知道 0~i-1
之间的分别的正负和即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5+5;
int n;
int sa, sb = 1; // 0~i-1 之间的 负 正 数个数,注意 s[0] 的符号是正,初始 sb 就有一个值,代表 s[0] 的符号为正
LL a, b; // a 为负方案数,b 为正方案数
int main() {
scanf("%d", &n);
int s = 1;
for (int i = 1; i <= n; i ++ ) {
int x;
scanf("%d", &x);
if (x < 0) s *= -1;
if (s < 0) a += sb, b += sa, sa ++ ;
else a += sa, b += sb, sb ++ ;
}
printf("%lld %lld\n", a, b);
return 0;
}
数学解法:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int n;
int s[N];
int s0, s1; //s0统计前缀和为偶数的个数,s1统计前缀和为奇数的个数
int main()
{
cin >> n;
s0 ++ ; //s[0]是偶数,直接加进去,省掉额外迭代一轮的操作
for (int i = 1; i <= n; ++ i)
{
int x;
cin >> x;
s[i] = s[i - 1] + (x < 0);
if (s[i] & 1) ++ s1;
else ++ s0;
}
cout << (LL)s0 * s1 << " " << (LL)s0 * (s0 - 1) / 2 + (LL)s1 * (s1 - 1) / 2 << endl;
return 0;
}