题目描述:
给你一个序列包含 n 个元素的序列 a1,a2,…,an (每个元素 ai≠0)。
你需要计算如下两个值:
- 有多少对数 (l,r)(l≤r) 满足 al⋅al+1…ar−1⋅ar 的结果为正;
- 有多少对数 (l,r)(l≤r) 满足 al⋅al+1…ar−1⋅ar 的结果为负。
即:这个序列中有多少子串(子串即连续子序列)的乘积为正,有多少子串的乘积为负。
输入格式
输入的第一行包含一个整数 n(1≤n≤2⋅105) —— 用于表示序列中元素的个数。
输入的第二行包含 n 个整数 a1,a2,…,an(−109≤ai≤109;ai≠0) ,用于表示序列中的元素。
输出格式
输出两个正数,以一个空格分隔。分别表示乘积为正的子串的个数,以及乘积为负的子串的个数。
样例输入1:
5
5 -3 3 -1 1
样例输出:
8 7
样例输入2:
10
4 2 -4 3 1 2 -4 3 2 3
样例输出:
28 27
问题分析:
本题使用动态规划来求解,我们定义状态:
f[i][0] 表示以a[i]结尾(或者说包含a[i])的乘积为正的字串个数;
f[i][1] 表示以a[i]结尾(或者说包含a[i])的乘积为正的字串个数;
注意了,这里说的以a[i]结尾的乘积为正的字串个数,指的不是说从a0到ai这个序列的子序列中乘积为正的子序列的个数,
而是说,这些子序列必须包含a[i],必须以a[i]结尾。
我们可以得出状态转移方程:
- 当ai < 0时:
- f[i][0] = f[i-1][1]
- f[i][1] = f[i-1][0] + 1
- 当ai > 0 时:
- f[i][0] = f[i-1][0] +1
- f[i][1] = f[i-1][1]
首先再强调一下,f[i][0]所对应的字串必须以a[i]结尾,
- 当 ai < 0 时,其实f[i][0]就是 以ai-1结尾的乘积为负数的各个字串加上尾部数字ai组成的各个新字串,负负得正,这样乘积重新变成正数了,所以就是f[i][0] = f[i-1][1];
- 同理,f[i][1] 就是以ai-1结尾的乘积为正数的各个字串 加上尾部数字ai组成的各个新字串,再加上ai这个负数本身,所以就是f[i][1] = f[i-1][0] + 1;
- 当ai > 0 时,f[i][0]就是 以ai-1结尾的乘积为正数的各个字串加上尾部数字ai组成的各个新字串,正数和正数相乘还是正数,同时再加上ai这个正数本身,所以就是f[i][0] = f[i-1][0] +1
- 同理,f[i][1] 就是以ai-1结尾的乘积为负数的各个字串 加上尾部数字ai组成的各个新字串,所以就是f[i][1] = f[i-1][1]。
再举例子来说明,以样例数据为例
5
5 -3 3 -1 1
正 负 序列
1 0 5 序列只有5,乘积为正的是1个,乘积为负的是0个
0 2 5 -3 序列加入-3,以-3结尾的乘积为正的子序列是0个
1 2 5 -3 3 序列加入3,以3结尾的乘积为正的子序列是1个
2 2 5 -3 3 -1
3 2 5 -3 3 -1 1
我们以一种从后往前的思路的去推导,每增加一个ai,就考虑ai和前面数据的关系,当然这种动态规划的思想确实有点难度,需要多练习。
程序如下:
#include <bits/stdc++.h>
using namespace std;
const int maxnum = 5001;
int n,a[maxnum];
long long f[maxnum][2], res1=0,res2=0;
int main() {
cin >> n;
for (int i = 1; i <= n; i ++){
cin >> a[i];
}
for (int i = 1; i <= n; i ++) {
if (a[i] > 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;
}
res1 += f[i][0];
res2 += f[i][1];
}
cout << res1 << " and "<< res2 <<endl;
return 0;
}
同时这道题也有一个变种,比如说输入的只有1 和 -1 两种数字,判断乘积为正的子序列的个数。