思路分析:
首先的想法就是暴力求解,也就是遍历任意两点之间的元素和,找到最小的和。但是直觉告诉我这样做肯定会超时,所以我根本没写。
可以怎么改进?暴力解应该是O(n^3)
的复杂度,其中有一轮循环是遍历i ~ j
之间的元素。这一部分可以简化:在读入数据时顺便维持一个数组arr
,该数组的每个元素存放从序列开头到该位置的元素和,这样i ~ j
之间的元素和就可以直接用arr[j] - arr[i]
求得。这样复杂度应该变为O(n ^ 2 + n) = O(n ^ 2)
。
其实一般我能想到这里我就满足了,但是仍然可以继续优化。
之前的思路是,对每一个i
,j
将从i+1
遍历到序列末尾,来找到最接近m
的那个j
。这其实就是个查找的过程,而查找我们知道,如果能用二分查找而不是线性遍历来查找,那么又会降低复杂度。而这里刚好满足二分查找的条件:arr
数组是递增的。这样一来,寻找合适的j
就可以用二分查找来代替,复杂度应变为O(nlogn + n)
。
代码象征性贴一下,来自柳神和开头的博客:
#include<iostream>
#include<vector>
const int MAX = 100001;
using namespace std;
int arr[MAX], des[MAX];
int findBestSum(int i, int n, int m) {
int left = i + 1;
int right = n;
int mid;
//为什么能用二分查找?因为arr数组是递增的
while (left < right) {
mid = (left + right) / 2;
if (arr[mid] - arr[i] >= m) {
right = mid;
}
else left = mid + 1;
}
des[i] = right;
return arr[right] - arr[i];
}
int main() {
int n, m;
cin >> n >> m;
int i, j;
arr[0] = 0;//0处不存有效值
for (i = 1; i <= n; i++) {
cin >> arr[i];
arr[i] += arr[i - 1];
}
vector<int> vec;
int res, mm = 0x7fffffff;
for (int i = 0; i < n; i++) {
res = findBestSum(i, n, m);
if (res >= m) {
if (res == mm) {
vec.push_back(i);
}
else if (res < mm) {
mm = res;
vec.clear();
vec.push_back(i);
}
}
else {
break;
}
}
for (i = 0; i < vec.size(); i++) {
printf("%d-%d\n", vec[i] + 1, des[vec[i]]);
}
return 0;
}