数组小和(归并分治)
数组小和(归并分治)
一、题目描述
数组小和的定义:
小和 = Σ f[i]
其中 f[i] 表示第 i 个数左侧小于等于 s[i] 的所有数的和。
例如数组 s = [1, 3, 5, 2, 4, 6]:
s[0]=1:左侧没有数,f[0]=0s[1]=3:左侧 ≤3 的数有 1,f[1]=1s[2]=5:左侧 ≤5 的数有 1, 3,f[2]=4s[3]=2:左侧 ≤2 的数有 1,f[3]=1s[4]=4:左侧 ≤4 的数有 1, 3, 2,f[4]=6s[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]的元素之和
这样一次合并就能统计所有「跨越左右」的贡献。
三、算法流程
- 递归函数
f(L, R):- 如果
L == R,返回 0 - 计算
左部分的答案 + 右部分的答案 + 跨越左右产生的答案,即返回f(L, mid) + f(mid+1, R) + merge(L, mid, R)
- 如果
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) |
| 适用场景 | 需要统计「左右之间」某种关系的计数问题 |

浙公网安备 33010602011771号