[前缀和] aw3574. 乘积数量(前缀和+组合数学)

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值