能量共振

题目分析

给定一个长度为 的整数数组,找到最短的子数组,使得该子数组可以被分成连续的两段,每段的元素之和都恰好为 。输出最短长度和对应的子数组个数,若不存在则输出 -1 -1

思路

前缀和 + 哈希分组

关键观察:如果子数组 能在位置 处被分成两段零和子数组,即 ,那么用前缀和 来表示就是:

$$

也就是说

问题转化:在前缀和数组中,找到三个下标 ,使得 ,对应的子数组长度为 ,我们要最小化这个长度。

具体做法:

  1. 计算前缀和数组 ,其中
  2. 将所有下标按前缀和的值分组(用哈希表)。
  3. 对于每个组内的下标列表(已天然有序),枚举所有相邻三元组 ,计算跨度
  4. 取全局最小跨度即为答案长度,同时统计个数。

为什么只需看相邻三元组?因为对于固定的首尾 ,中间的 只要存在一个即可。而要让 最小,最优的 一定出现在排序后的相邻位置附近。更准确地说,对于任何跨度为 的三元组 ,相邻三元组 的跨度不会更大,因此只检查相邻三元组即可。

代码

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    scanf("%d", &n);
    vector<long long> pre(n + 1);
    pre[0] = 0;
    for (int i = 1; i <= n; i++) {
        long long x;
        scanf("%lld", &x);
        pre[i] = pre[i - 1] + x;
    }
    // 按前缀和的值分组,记录出现的下标
    unordered_map<long long, vector<int>> mp;
    for (int i = 0; i <= n; i++) {
        mp[pre[i]].push_back(i);
    }
    int minLen = INT_MAX;
    int cnt = 0;
    for (auto& [val, indices] : mp) {
        if (indices.size() < 3) continue;
        for (int i = 0; i + 2 < (int)indices.size(); i++) {
            int span = indices[i + 2] - indices[i];
            if (span < minLen) {
                minLen = span;
                cnt = 1;
            } else if (span == minLen) {
                cnt++;
            }
        }
    }
    if (minLen == INT_MAX) {
        printf("-1 -1\n");
    } else {
        printf("%d %d\n", minLen, cnt);
    }
    return 0;
}
import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine().trim());
        StringTokenizer st = new StringTokenizer(br.readLine().trim());
        long[] pre = new long[n + 1];
        pre[0] = 0;
        for (int i = 1; i <= n; i++) {
            pre[i] = pre[i - 1] + Long.parseLong(st.nextToken());
        }
        HashMap<Long, List<Integer>> mp = new HashMap<>();
        for (int i = 0; i <= n; i++) {
            mp.computeIfAbsent(pre[i], k -> new ArrayList<>()).add(i);
        }
        int minLen = Integer.MAX_VALUE;
        int cnt = 0;
        for (List<Integer> indices : mp.values()) {
            if (indices.size() < 3) continue;
            for (int i = 0; i + 2 < indices.size(); i++) {
                int span = indices.get(i + 2) - indices.get(i);
                if (span < minLen) {
                    minLen = span;
                    cnt = 1;
                } else if (span == minLen) {
                    cnt++;
                }
            }
        }
        if (minLen == Integer.MAX_VALUE) {
            System.out.println("-1 -1");
        } else {
            System.out.println(minLen + " " + cnt);
        }
    }
}

复杂度分析

  • 时间复杂度。计算前缀和、哈希分组、遍历相邻三元组,每步都是线性的。
  • 空间复杂度。前缀和数组和哈希表各占 空间。