跳到主要内容

HJ98. 喜欢切数组的红

题目描述​

小红有一个长度为 n 的数组,她打算将数组切两刀变成三个非空子数组,使得每一个子数组中至少存在一个正数,且每个子数组的和都相等。

求一共有多少种不同的切分方案。

输入格式​

  • 第 1 行:数组长度 n
  • 第 2 行:n 个整数(可为负数、零、正数)

输出格式​

一个整数,表示满足条件的切分方案数。

示例​

示例 1​

输入:

3
3 3 3

输出:

1

说明: 切成 [3] | [3] | [3],三段和都为 3 且各含正数。

示例 2​

输入:

6
1 1 4 5 1 4

输出:

0

示例 3​

输入:

10
0 3 4 2 3 2 1 -1 3 4

输出:

2

解题思路​

第一步:理解问题本质​

切两刀成三段,需要满足三个条件:

  1. 三段和相等 → 每段和 = 总和/3
  2. 三段都非空
  3. 每段至少含一个正数

三段和相等等价于:存在切点 i < j 使得前缀和 pre[i] == m、pre[j] == 2m(其中 m = total/3)。

第二步:暴力解法​

枚举所有 (i, j) 切点,计算三段和与各段正数个数,逐个判断。O(n²) 且要反复求和,大数据下超时。

第三步:前缀和 + 正数计数优化​

用前缀和数组 pre 和正数个数前缀数组 pos,把"区间和"和"区间是否含正数"都变成 O(1) 查询:

  • 第一段含正数:pos[i] > 0
  • 中间段含正数:pos[j] - pos[i] > 0
  • 第三段含正数:pos[n] - pos[j] > 0

第四步:最优解法(扫描 + 树状数组)​

对每个合法的 j(pre[j] == 2m 且第三段含正数),需要统计满足以下条件的 i 个数:

  • i < j,pre[i] == m,pos[i] > 0,且 pos[i] < pos[j]

从左到右扫描位置 p,用树状数组维护已见到的合法 i 的 pos 值。遇到 j 时查询 pos 值 < pos[j] 的个数累加到答案。O(n log n)。


完整代码实现​

算法可视化 · 逐帧回放这道题的代码49 帧 · 录制输入
import sys


class Fenwick:
def __init__(self, n):
self.n = n
self.bit = [0] * (n + 2)

def add(self, idx, val):
idx += 1
while idx <= self.n + 1:
self.bit[idx] += val
idx += idx & -idx

def query(self, idx):
idx += 1
s = 0
while idx > 0:
s += self.bit[idx]
idx -= idx & -idx
return s


def solve():
data = sys.stdin.buffer.read().split()
n = int(data[0])
a = list(map(int, data[1:1 + n]))

total = sum(a)
if total % 3 != 0:
print(0)
return
m = total // 3

pre = [0] * (n + 1)
pos = [0] * (n + 1)
for i in range(1, n + 1):
pre[i] = pre[i - 1] + a[i - 1]
pos[i] = pos[i - 1] + (1 if a[i - 1] > 0 else 0)

if pos[n] == 0:
print(0)
return

bit = Fenwick(n + 1)
ans = 0
for p in range(1, n):
if pre[p] == 2 * m and pos[n] - pos[p] > 0:
ans += bit.query(pos[p] - 1)
if pre[p] == m and pos[p] > 0:
bit.add(pos[p], 1)
print(ans)


if __name__ == "__main__":
solve()

示例推演​

以示例 3 [0,3,4,2,3,2,1,-1,3,4] 为例:

总和 = 21,m = 7,2m = 14。

前缀和 pre:

位置 p12345678910
pre0379121415141721
pos0123456678
  • 合法 i(pre == 7 且 pos[i]>0):位置 3,pos=2
  • 合法 j(pre == 14 且第三段含正数 pos[10]-pos[j]>0):位置 6(pos=5)、位置 8(pos=6)

扫描:

  • 位置 6(j):此时已加入的 i 只有位置 3(pos=2)。pos[j]=5,需 pos[i]<5,位置 3 满足 → 贡献 1。
  • 位置 8(j):pos[j]=6,位置 3 的 pos=2<6 → 贡献 1。

结果:2。


复杂度分析​

解法时间复杂度空间复杂度说明
暴力枚举O(n²)O(1)超时
前缀和优化O(n²)O(n)仍需枚举
扫描 + 树状数组O(n log n)O(n)最优

n 为数组长度


易错点总结​

1. 忘记"每段含正数"​

仅三段和相等不满足题意,必须同时保证三段各自至少有一个正数。

2. 总和必须能被 3 整除​

若 total % 3 != 0,直接输出 0,提前返回。

3. i < j 的严格性​

第一段末尾 i 必须严格小于第二段末尾 j,保证中间段非空。扫描时先查询 j 再加入同位置的 i 可避免 i==j 被误计。

4. 负数与零​

数组含负数和 0,pos 只统计 > 0 的数;区间含正数等价于 pos 差分大于 0。


扩展思考​

  • 用树状数组维护 pos 值实现"计数 pos[i] < pos[j]"是该题的关键技巧,也可推广到二维偏序计数。
  • 若去掉"每段含正数"约束,题目退化为纯前缀和计数,可直接 O(n) 完成。

相关题目​

自测​

加载评论中...