原题:https://pintia.cn/problem-sets/994805342720868352/problems/994805439202443264
Sample Input 1:
16 15
3 2 1 5 4 6 8 7 16 10 15 11 9 12 14 13
Sample Output 1:
1-5
4-6
7-8
11-11
Sample Input 2:
5 13
2 4 5 7 9
Sample Output 2:
2-4
4-5
题目大意
给定一数字序列,找到其中一连续段,使得这个连续段内的数字和恰好等于给定价格M;若不能找到恰
好等于,则找到让自己付出价格最少的一个子区间(该区间的总金额大于等于需支付价格),并求所有可能得结果。
方法一(暴力搜索)
假定输入数字长度为N,则 i i i 从 1 到 N, 每次找从 i i i 开始满足条件得最小长度,下标记作 j j j。最后,输出满足条件且差值最小的区间端点。
/* 1044 Shopping in Mars (25 分) */
// 暴力求解,复杂度 O(n^2)
#include<vector>
#include<iostream>
using namespace std;
const int inf = 100000000;
typedef struct node {
int start;
int end;
}node;
vector<int> list; // 钻石序列
int minlost = inf; // 当前最小差距
vector<node> ans; // 答案序列
int main() {
int n, m, i, j;
scanf("%d%d", &n, &m);
list.resize(n);
for (i = 0; i < n; i++) scanf("%d", &list[i]);
// 暴力求解
for (i = 0; i < n; i++) {
int sum = 0;
for (j = i; j < n && sum < m; j++) sum += list[j]; // j 是序列的下一位
if (sum - m < minlost && sum >= m) {
ans.clear();
ans.push_back({ i,j });
minlost = sum - m;
}
else if (sum - m == minlost) ans.push_back({ i,j });
}
for (auto iter = ans.begin(); iter != ans.end(); iter++) {
node tmp = *iter;
printf("%d-%d\n", tmp.start + 1, tmp.end);
}
return 0;
}
由于涉及双重for
循环,所以复杂度为
O
(
n
2
)
O(n^{2})
O(n2),会有两个测试点超时。
改进(折半查找)
由于暴力搜索的复杂度为
O
(
n
2
)
O(n^{2})
O(n2),所以考虑改为二分查找
,其复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn)。
但是二分查找只适用于有序序列,所以考虑将输入的数字序列叠加起来,令数组sum[i]
表示输入数字num[1]到num[i]
的加和,这样,和序列即为非递减序列,然后利用二分查找搜索两个端点,具体做法使固定一个端点,再二分查找另一个端点,复杂度为
O
(
n
∗
l
o
g
n
)
O(n * logn)
O(n∗logn)。
注意:与普通的二分查找不一样的地方使,我们不是找到精确等于的地方,而是大于等于的位置
。
/* 1044 Shopping in Mars (25 分) */
// 拿空间换时间,遍历两次,复杂度为
#include<vector>
#include<iostream>
using namespace std;
const int inf = 100000000;
vector<int> sum; // 和序列
vector<int> ans; // 答案
int n, m;
void BinarySearch(int start, int& end, int& tmpsum) {
int left = start, right = n; // 总所有start后的位置找一个
while (left < right) {
int mid = (left + right) / 2;
// 表示[start,mid]区间的和
if (sum[mid] - sum[start - 1] < m) //终点扩大到右半段
left = mid + 1;
else right = mid; // 找大于等于的位置
}
end = right;
tmpsum = sum[end] - sum[start - 1];
}
int main() {
int i, j, tmp;
scanf("%d%d", &n, &m);
sum.resize(n + 1);
for (i = 1; i <= n; i++) {
scanf("%d", &sum[i]);
sum[i] += sum[i - 1]; // sum[0] 初始为0
}
int minlost = inf; // 最小差距
// 开始折半查找
for (i = 1; i <= n; i++) { // 起点
BinarySearch(i, j, tmp);
if (tmp >= m) {
if (tmp - m < minlost) {
ans.clear();
minlost = tmp - m;
ans.push_back(i);
ans.push_back(j);
}
else if (tmp - m == minlost) {
ans.push_back(i);
ans.push_back(j);
}
}
}
for (i = 0; i < ans.size(); i += 2)
printf("%d-%d\n", ans[i], ans[i + 1]);
system("pause");
return 0;
}
最终通过测试: