题目
给定一个长度为n的数组 a1,a2,…,an。
现在,要将该数组从中间截断,得到三个非空子数组。
要求,三个子数组内各元素之和都相等。
请问,共有多少种不同的截断方法?
输入格式
第一行包含整数 n。
第二行包含 n 个整数 a1,a2,…,an。
输出格式
输出一个整数,表示截断方法数量。
数据范围
前六个测试点满足 1 ≤ n ≤ 10。
所有测试点满足 1 ≤ n ≤ 1 0 5 10^{5} 105 ,−10000 ≤ ai ≤ 10000。
样例
输入样例1:
4
1 2 3 3
输出样例1:
1
输入样例2:
5
1 2 3 4 5
输出样例2:
0
输入样例3:
2
0 0
输出样例3:
0
思路
题目要求:
1.分成三个非空子数组
2.三个子数组内各元素之和都相等
必然可以通过特判元素总和是否为3的倍数来判断是否合题意。
接下来我们就要考虑如何划分是合理的,有几种划分方式。
枚举第一刀时:
因为 i 后面还有( i + 1 , j - 1 ) 和 ( j , n ) 两段元素所以最大为 n-2 。
j 的前面有两段元素,所以最小为3。
所以 j = i + 2。
当第一段( 1 , i ) 的元素值总和 为 所有元素值总和的三分之一时 第一刀划分符合题意,
然而我们还需要判断第二刀划分后 ( j , n ) 这段的数总和是否也等于总和的三分之一,那么最方便的就是前缀和预处理。
for(int i=1;i<=n;i++){
cin >> s[i];
s[i]=s[i-1]+s[i];
}
前缀和中求 i 到 j 范围的数总和为s [ j ] - s [ i - 1 ]
那么求 ( j , n ) 这段的数总和则为s [ n ] - s [ j - 1 ] 也就是 s [ n ] - s [ i + 1 ]
for(int i = 1 ;i <= n-2; i++){
if(s[i] == average) cnt++;
if(s[n]-s[i+1] == average) ans += cnt;
}
枚举第二刀时:
同理可得i和j的取值范围
易得 ( i , n ) 这段的数总和为s [ n ] - s [ i - 1 ]
for(int i = 3 ;i <= n; i++){
if(s[i-2] == average) cnt++;
if(s[n]-s[i-1] == average) ans += cnt;
}
完整代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int n;
LL s[N];
int main(){
cin >> n;
for(int i = 1;i <= n;i++){
cin >> s[i];
s[i] += s[i-1];
}//创造前缀和数组,因为最大10^5*10^5>2*10^9,所以不可以用int存
if(s[n] % 3 != 0 || n < 3){//所有和不是三的倍数或者是个数小于三输出0
cout << "0" << endl;
return 0;
}
int average = s[n] / 3,cnt = 0;
LL ans = 0;//可能超范围
for(int i = 1 ;i <= n-2; i++){//判断第一刀时
if(s[i] == average) cnt++;
if(s[n] - s[i+1] == average) ans += cnt;
}
/*
for(int i = 3 ;i <= n; i++){//判断第二刀时
if(s[i-2] == average) cnt++;
if(s[n] - s[i-1] == average) ans += cnt;
}
*/
cout << ans << endl;
return 0;
}
如有不对,欢迎指正
都看到这了,不点个赞嘛 O 。0 ?