我们假设 pre[i]: 数组前i个数的之和,这就是前缀和

计算所有下标对 (1 <= l <= r <= n) pre[r] - pre[l] 之和

如果数据量是 n <= 1e5,直接两个for循环暴力求解的话,时间复杂度O(N^2),太慢了

暴力方法

点击查看代码
ll brute(){
	ll ans = 0;
	for(int l = 1;l<=n;l++){
		for(int r = l+1;r<=n;r++){
			ans += pre[r] - pre[l];
		}
	}
	return ans;
}

我们仔细观察,可以发现,
整个过程就是 :
ans += pre[i] - pre[j] (i固定,1 <= j < i),ans +=pre[j]-pre[i](i固定,i < j <= n)
通过这个式子,我们可以得到这个结论: -pre[i]出现了 n - i 次,+pre[i] 出现了 i - 1次
那么我们可以根据这个结论,遍历一遍数组,进行求和就好
时间复杂度为O(N)

点击查看代码
ll go(){
	ll ans = 0;
	for(int i = 1;i<=n;i++){
	// 这里可以整合成这样:ans += pre[i] * (2 * i - n - 1);
		ans += pre[i] * (i - 1); // +pre[i] 出现了 i - 1次
		ans -= pre[i] * (n - i); // -pre[i]出现了 n - i 次
	}
	return ans;
}

计算所有下标对 (1 <= l <= r <= n) |pre[r] - pre[l]| 之和

这里的||代表取绝对值

暴力方法

点击查看代码
ll brute(){
	ll ans = 0;
	for(int l = 1;l<=n;l++){
		for(int r = l+1;r<=n;r++){
			ans += std::abs(pre[r] - pre[l]);
		}
	}
	return ans;
}

如何优化呢?
我们再观察一下:
整个过程就是 :
ans += |pre[i] - pre[j]| (i固定,1 <= j < i),ans += |pre[j]-pre[i]| (i固定,i < j <= n)
根据绝对值的性质,如果pre[i] 比 pre[j]大,那么我们可以认为pre[j] 是减去的,pre[i]是加上的
否则就是pre[j] 是加上,pre[i]就是减去,结果和 |pre[i] - pre[j]| 是一样的
那么,在整个过程中,有多少个pre[i]是减去和以及多少个pre[i]是加上的呢?
我们可以先排序
这样,大小关系就明确了,那么对于每一个pre[i]:
比pre[i]小的有 i - 1 个,也就意味着有 i - 1 个 pre[i] 应该加上
比pre[i]大的有 n - i 个,也就意味着有 n - i 个 pre[i] 应该减去
那么我们可以根据这个结论,去解决这个问题
时间复杂度为O(N* log N) ,因为排序时间复杂度是这个

点击查看代码
ll go(){
	ll ans = 0;
	std::sort(pre+1,pre+1+n);
	for(int i = 1;i<=n;i++){
		ans += pre[i] * (i - 1);
		ans -= pre[i] * (n - i);
	}
	return ans;
}

可以发现,这和上面那个求pre[r] - pre[l]总和代码是一样的,只是多了个排序而已。

进一步:如何O(N)求所有子数组的和?

你可以仔细观察 计算所有下标对 (1 <= l <= r <= n) pre[r] - pre[l] 之和,这里实际上就是在求[2,n]范围内的所有子数组之和
那么如求[1,n]范围内的所有子数组之和呢?
只要加上所有的 pre[i] (1 <= i <= n) 就好了
这个时候,+pre[i] 就出现了i次,-pre[i] 依旧出现了 n - i次
代码如下,时间复杂度:O(N)

点击查看代码
ll go(){
	ll ans = 0;
	for(int i = 1;i<=n;i++){
	// 这里可以整合成这样:ans += pre[i] * (2 * i - n - 1);
		ans += pre[i] * i; // +pre[i] 出现了 i - 1次
		ans -= pre[i] * (n - i); // -pre[i]出现了 n - i 次
	}
	return ans;
}
posted on 2025-09-16 14:24  tomQian  阅读(18)  评论(0)    收藏  举报