数组小和(归并分治)

数组小和(归并分治)

一、题目描述

数组小和的定义:

小和 = Σ f[i]

其中 f[i] 表示第 i 个数左侧小于等于 s[i] 的所有数的和。

例如数组 s = [1, 3, 5, 2, 4, 6]:

  • s[0]=1:左侧没有数,f[0]=0
  • s[1]=3:左侧 ≤3 的数有 1,f[1]=1
  • s[2]=5:左侧 ≤5 的数有 1, 3,f[2]=4
  • s[3]=2:左侧 ≤2 的数有 1,f[3]=1
  • s[4]=4:左侧 ≤4 的数有 1, 3, 2,f[4]=6
  • s[5]=6:左侧 ≤6 的数有 1, 3, 5, 2, 4,f[5]=15

小和 = 0 + 1 + 4 + 1 + 6 + 15 = 27

二、思路分析

暴力做法

对每个 i,遍历它左侧所有元素,把 ≤ s[i] 的加起来。

时间复杂度 O(n²),n = 10^5 时超时。

归并分治优化

小和统计可以转化为:

对每个数 s[j],统计它左侧所有 ≤ s[j] 的数的和,累加起来。

归并排序合并两个有序区间 [L, mid] 和 [mid+1, R] 时:

  • 左半和右半内部各自已经有序
  • 对于右半的每个元素 arr[j],左半中所有 ≤ arr[j] 的元素都在它的左侧(因为归并保持了原序列的相对顺序)
  • 用双指针在左半区间维护一个前缀和 sum,表示当前左半中所有 ≤ arr[j] 的元素之和

这样一次合并就能统计所有「跨越左右」的贡献。

三、算法流程

  1. 递归函数 f(L, R):
    • 如果 L == R,返回 0
    • 计算 左部分的答案 + 右部分的答案 + 跨越左右产生的答案,即返回f(L, mid) + f(mid+1, R) + merge(L, mid, R)
  2. merge(L, mid, R) 做两件事:
    • 统计:对每个 j ∈ [mid+1, R],累加左半中 ≤ arr[j] 的元素和
    • 归并:把两个有序区间合并到 help,再拷回 arr

四、AC 代码

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

vector<int> arr;
const int N = 1e5 + 5;
vector<int> help(N);   // 归并时用的辅助数组
typedef long long ll;

// merge: 合并 arr[L..mid] 和 arr[mid+1..R],同时统计跨越左右的小和
ll merge(int L, int mid, int R)
{
    ll ans = 0, sum = 0;
    // 双指针不回退,简便运算 
    for (int j = mid + 1, i = L; j <= R; j++)
    {
        // 把左半中所有 ≤ arr[j] 的元素累加进 sum
        while (i <= mid && arr[i] <= arr[j])
        {
            sum += arr[i];
            i++;
        }
        ans += sum;  // sum 就是 arr[j] 左侧 ≤ arr[j] 的所有元素和
    }

    // ===== 归并部分 =====
    int t = L;
    int a = L, b = mid + 1;
    while (a <= mid && b <= R)
        help[t++] = (arr[a] <= arr[b] ? arr[a++] : arr[b++]);
    while (a <= mid) help[t++] = arr[a++];
    while (b <= R) help[t++] = arr[b++];

    for (int i = L; i <= R; i++) arr[i] = help[i];

    return ans;
}

// f: 递归处理区间 [L, R],返回该区间内部的小和贡献
ll f(int L, int R)
{
    if (L == R) return 0;
    int mid = (L + R) / 2;
    // 左半 + 右半 + 跨左右
    return f(L, mid) + f(mid + 1, R) + merge(L, mid, R);
}

int main()
{
    // 读入形如 "[1,3,5,2,4,6]" 的字符串,提取其中的整数
    string s;
    getline(cin, s);
    for (char &c : s)
        if (!isdigit(c) && c != '-') c = ' ';
    istringstream ss(s);
    int x;
    while (ss >> x) arr.push_back(x);
    int len = arr.size();
    cout << f(0, len - 1) << endl;
    return 0;
}

五、总结

要点 内容
核心思想 归并分治,在合并时顺便统计跨左右的信息
关键操作 sum 维护左半 ≤ arr[j] 的元素和
复杂度 O(n log n)
适用场景 需要统计「左右之间」某种关系的计数问题
posted @ 2026-10-10 11:34  leaderling  阅读(17)  评论(0)    收藏  举报