题解:学而思编程 平衡点

【题目来源】

学而思编程:平衡点

【题目描述】

给定一个由 \(n\) 个整数组成的数列 \(a_1,a_2,\dots,a_n\),请为这个数列找到一个平衡点,使得平衡点左侧与右侧的引力尽量接近。

若平衡点为 \(k\),则左侧引力定义为数列中下标小于 \(k\) 的各个元素到 \(a_k\) 的距离乘以这些元素大小的总和。同理,右侧引力定义为数列中下标大于 \(k\) 的每个元素到 \(a_k\) 的距离乘以这些元素大小的总和。

例如:\(n=6\),若选择 \(a_3\) 为平衡点,左侧的引力计算公式为:

\(L=(3-1)\times a_1+(3-2)\times a_2\)

右侧的引力计算公式为:

\(R=(4-3)\times a_4+(5-3)\times a_5+(6-3)\times a_6\)

请你找到一个最佳平衡点 \(k\),并输出平衡点的位置和选择该点为平衡点时左右引力之差绝对值的最小值。

【输入】

第一行一个整数 \(n\)

第二行 \(n\) 个整数 \(a_1,a_2,\dots,a_n\)

【输出】

一行一个整数,表示平衡点的左右引力之差绝对值的最小值。

【输入样例】

4
1 2 3 4

【输出样例】

0

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的数列 \(a_1, a_2, \ldots, a_n\),选择平衡点 \(k\)\(2 \leq k \leq n-1\)),左侧引力 \(L_k = \sum_{i=1}^{k-1} (k-i) \times a_i\),右侧引力 \(R_k = \sum_{i=k+1}^{n} (i-k) \times a_i\)。求 \(\min |L_k - R_k|\)。这是一个前缀和 + 递推优化问题,关键在于将带权距离和转化为前缀和的累加形式。

  2. 算法选择

    • 前缀和预处理sum_f[i] 为前 \(i\) 项和,sum_b[i] 为后 \(i\) 项和
    • 递推计算引力:利用 \(L_k\)\(L_{k-1}\)\(R_k\)\(R_{k+1}\) 的递推关系,将 \(O(n^2)\) 的暴力计算优化到 \(O(n)\)
  3. 关键步骤

    • 初始化:读取 \(n\) 和数组 \(a[1..n]\)
    • 计算前缀和与后缀和
      • sum_f[i] = sum_f[i-1] + a_i\(1 \leq i \leq n\)):前 \(i\) 项元素之和
      • sum_b[i] = sum_b[i+1] + a_i\(n \geq i \geq 1\)):从第 \(i\) 项到第 \(n\) 项之和
    • 递推计算左侧引力 \(L_k\)\(k\)\(2\)\(n\)):
      • \(L_k = \sum_{i=1}^{k-1} (k-i) \times a_i = \sum_{i=1}^{k-1} \sum_{j=i}^{k-1} a_i = L_{k-1} + sum_f[k-1]\)
      • L[k] = L[k-1] + sum_f[k-1]:前 \(k-1\) 个元素的前缀和之和
    • 递推计算右侧引力 \(R_k\)\(k\)\(n-1\)\(1\)):
      • \(R_k = \sum_{i=k+1}^{n} (i-k) \times a_i = R_{k+1} + sum_b[k+1]\)
      • R[k] = R[k+1] + sum_b[k+1]:后 \(n-k\) 个元素的后缀和之和
    • 枚举求最小差值
      • 遍历 \(k\)\(2\)\(n-1\)ans = \min(ans, |L[k] - R[k]|)
    • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),四次线性遍历
    • 空间复杂度:\(O(n)\),前缀和、后缀和、\(L\)\(R\) 四个辅助数组
  5. 前缀和递推的核心思想

    • 带权距离和转化\((k-i) \times a_i = a_i + a_i + \cdots + a_i\)(共 \(k-i\) 次),即 \(a_i\)\(L_k\) 中被累加 \(k-i\) 次。当 \(k\) 增加 \(1\) 时,所有前 \(k-1\) 项的权重都增加 \(1\),新增贡献恰好为 \(sum_f[k-1]\)
    • 递推关系建立\(L_k = L_{k-1} + \sum_{i=1}^{k-1} a_i = L_{k-1} + sum_f[k-1]\),避免每次重新计算双重循环
    • 对称处理:右侧引力与左侧完全对称,通过后缀和递推实现
    • 离线预处理:先计算所有位置的 \(L_k\)\(R_k\),最后统一枚举求最小值,避免重复计算
    • 适用于"带权距离和 + 中轴点选择"类问题,前缀和递推可将双重求和优化为线性复杂度

【解题思路】

【算法标签】

前缀和

【代码详解】

#include <bits/stdc++.h>
using namespace std;
long long n, a[300005], sum_f[300005], sum_b[300005], L[300005], R[300005], ans = 9e18;

int main()
{
    cin >> n;  // 输入数组长度
    for (int i = 1; i <= n; i++)  // 输入数组元素
    {
        cin >> a[i];
    }

    // 计算前缀和
    for (int i = 1; i <= n; i++)
    {
        sum_f[i] = sum_f[i - 1] + a[i];
    }

    // 计算后缀和
    for (int i = n; i >= 1; i--)
    {
        sum_b[i] = sum_b[i + 1] + a[i];
    }

    // 计算L[k]:前k-1个元素的所有前缀和之和
    for (int k = 2; k <= n; k++)
    {
        L[k] = L[k - 1] + sum_f[k - 1];
    }

    // 计算R[k]:后n-k个元素的所有后缀和之和
    for (int k = n - 1; k >= 1; k--)
    {
        R[k] = R[k + 1] + sum_b[k + 1];
    }

    // 遍历所有分割点k,求|L[k] - R[k]|的最小值
    for (int k = 2; k <= n - 1; k++)
    {
        ans = min(ans, abs(L[k] - R[k]));
    }

    cout << ans << endl;  // 输出最小差值
    return 0;
}

【运行结果】

4
1 2 3 4
0
posted @ 2026-07-04 14:27  团爸讲算法  阅读(5)  评论(0)    收藏  举报