算法:前缀和—截断数组

132 阅读3分钟

3956. 截断数组 - AcWing题库

题目描述

给定一个长度为 nn 的数组 a1,a2,,ana1,a2,…,an

现在,要将该数组从中间截断,得到三个非空子数组。

要求,三个子数组内各元素之和都相等。

请问,共有多少种不同的截断方法?

题目解析

本题目的大意是说,有一个数组,长度为n,现在将这个数组分成三个部分,左中右,使得这三个部分的和都相等

思路

首先根据数据范围是10510^5,算法的时间复杂度应为nlogn,直接进行暴力枚举,枚举第一个分割点和第二个分割点的位置的话 tletle,所以,最多只能枚举一个分割点

我们假设枚举第二个分割点,设为j,由于要保证每个子数组都不为空,因此就要将j从第二个数开始枚举,j表示第二个区间的最后一个数,因此,答案就是将j从2枚举到n - 1(要保证最后一个区间也不空)的合法方案的总和,如下图(来源y总的讲解);

3.png

假设第二个分割点在j的位置,此时j固定,第一个分割点要保证i前面的数等于数组总和的三分之一,而第二个分割点要保证j前面的数总和等于数组总和的三分之二

因此在 j 从前向后遍历的过程中,我们要记录第一个分割点假设 i 前面的总数的和等于 S / 3 的个数,我们用cnt来存,而总的方案数使用res来存,在第一个分割点合法个数是cnt的情况下,假设前 j 个数和等于 S / 3 * 2,那么就可以将cnt加到res中,这是这个j下对应的合法数,而后再继续判断下一个j

代码如下

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;
int n, s[N];    // s是前缀和数组

typedef long long LL;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        s[i] = s[i - 1] + x;    // 前缀和
    }
    
    LL cnt = 0, res = 0;
    if (s[n] % 3) puts("0");    // 如果总和不是3的倍数,必定不满足条件
    else
    {
        for (int j = 2; j < n; j++)
        {
            if (s[j - 1] == s[n] / 3) cnt++;    // 判断在当前j下,有多少个第一个分割点满足条件,在j每次向后扫描的过程中,只需要判断第s[j - 1]是否等于S / 3即可,因为前面的都在前面的循环中计算过了,合法的已经加入到了cnt中
            if (s[j] == s[n] / 3 * 2) res += cnt;    // 在判断一下第二个分割点是否满足条件,如果满足,那么在当前的j下,cnt个第一个分割点都满足情况,都可以加入到res中
        }
        
        cout << res << endl;
    }
    
    
    
    return 0;
}

另外,由于int的数据范围最大到2 x 10910^9,所以需要开long long

求前缀和代码

for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + a[i];

🆗,第一次博客到此结束!