题目描述
给定一个长度为 的数组 。
现在,要将该数组从中间截断,得到三个非空子数组。
要求,三个子数组内各元素之和都相等。
请问,共有多少种不同的截断方法?
题目解析
本题目的大意是说,有一个数组,长度为n,现在将这个数组分成三个部分,左中右,使得这三个部分的和都相等
思路
首先根据数据范围是,算法的时间复杂度应为nlogn,直接进行暴力枚举,枚举第一个分割点和第二个分割点的位置的话 ,所以,最多只能枚举一个分割点
我们假设枚举第二个分割点,设为j,由于要保证每个子数组都不为空,因此就要将j从第二个数开始枚举,j表示第二个区间的最后一个数,因此,答案就是将j从2枚举到n - 1(要保证最后一个区间也不空)的合法方案的总和,如下图(来源y总的讲解);
假设第二个分割点在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 ,所以需要开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];
🆗,第一次博客到此结束!