题目
给定一个长度为 的数组 。
现在,要将该数组从中间截断,得到三个非空子数组。
要求,三个子数组内各元素之和都相等。
请问,共有多少种不同的截断方法?
输入格式
第一行包含整数 。
第二行包含 个整数 。
输出格式
输出一个整数,表示截断方法数量。
数据范围
前六个测试点满足 。
所有测试点满足 , 。
输入样例1:
4
1 2 3 3
输出样例1:
1
输入样例2:
5
1 2 3 4 5
输出样例2:
0
输入样例3:
2
0 0
输出样例3:
0
解题
方法一:前缀和 枚举
思路
由【转载】由数据范围反推算法复杂度以及算法内容可以看出这题的时间复杂度要控制在 之内,那么也就不能采取暴力枚举两个截断点这样 的做法了。
首先,如果数组的长度小于 或数组所有元素和不能被 整除时一定不存在截断方法,直接输出 。然后可以把数组预处理为前缀和数组以快速的判断子数组和。再然后枚举第一个截断点,并处理为前缀和数组,这样就可以在最后枚举第二个截断点时以 的时间复杂度快速地查询有多少个第一截断点可以与其搭配为截断方案。
例如(以下表述数组下标均从 开始),当 时:
- 首先判断其长度大于 ,且 说明该数组有截断方案。
- 然后把它处理为前缀和数组: 。
- 从 中枚举其第一个截断点得到截断点数组 ,这表示:
- 在下标 与 之间可以存在一个截断点使 被截断为: 。
- 在下标 与 之间可以存在一个截断点使 被截断为: 。
- 把 处理为前缀和数组: 。
- 从 中枚举其第一个截断点:
- 当 时, ,说明在下标 与 之间可以存在一个截断点使 被截断为: ,查截断点数量前缀和数组 得第二截断点选为此处时有 个第一截断点可供选择,故截断方法数量增加 (
cnt += cs[i - 1]
)。 - …
- 当 时, ,说明在下标 与 之间可以存在一个截断点使 被截断为: ,查截断点数量前缀和数组 得第二截断点选为此处时有 个第一截断点可供选择,故截断方法数量增加 (
cnt += cs[i - 1]
)。 - …
- 当 时, ,说明在下标 与 之间可以存在一个截断点使 被截断为: ,查截断点数量前缀和数组 得第二截断点选为此处时有 个第一截断点可供选择,故截断方法数量增加 (
注意:数组长度为 时,截断方法数量最大可能到 造成整数溢出,所以 cnt
要用长整形保存。
代码
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
in.nextToken();
int n = (int) in.nval;
if (n < 3) {
System.out.println(0);
return;
}
int[] s = new int[n + 1];
for (int i = 1; i <= n; ++i) {
in.nextToken();
s[i] = s[i - 1] + (int) in.nval;
}
if (s[n] % 3 != 0) {
System.out.println(0);
return;
}
int avg = s[n] / 3;
int[] cs = new int[n + 1];
for (int i = 2; i <= n - 1; ++i) {
if (s[i - 1] == avg) cs[i] = 1;
cs[i] += cs[i - 1];
}
long cnt = 0;
for (int i = n; i >= 3; --i) {
if (s[n] - s[i - 1] == avg) {
cnt += cs[i - 1];
}
}
System.out.println(cnt);
}
}
评论区