题意
给出一个序列和一个定值M,要求找出和为M的子串,如果没有这样的子串,那么要求找出和大于M且和最小的子串
标准输入
16 15
3 2 1 5 4 6 8 7 16 10 15 11 9 12 14 13
标准输出
1-5
4-6
7-8
11-11
思路
- 其实这是一个求子列和的问题,不过有一些限制,不必列举所有的子列
以第i位(1≤i≤N)作为开头的有N个子串,而其中至多只有一个子串是符合条件的
因为:子串和是单调递增的(所有项都是正整数),而要求是找到和大于等于给定数字,且和最小的子串,因此第一个使和达到要求的子串便是以这位数为开头的符合要求的子串
Example
16 15
3 2 1 5 4 6 8 7 16 10 15 11 9 12 14 13
以i=1第一位数3开头为例,要求的值是15,不断往后加,3+2+1+5+4 此时是首次达到大于等于15这个要求,而继续往后加必然是不满足最小这个要求的,因此3开头的子列只有这一个,然后继续看下一位2开头的子列。
以这种算法的话,最坏情况下复杂度是O(n²),而这道题对运行时间有严格要求,如果将序列改写成增量数组的话,便可以使数组成为有序的,因此可以用二分查找,使程序时间复杂度降到O(nlgn)
- 增量数组
原序列
3 2 1 5 4 6 8
增量序列
3 5 6 11 15 21 29 实现代码
#include <iostream> #include <string> #include <vector> #include <queue> #include <cmath> #include <cstdio> #include <array> #include <algorithm> #include <set> #include <vector> using namespace std; pair<pair<int, int>, int> Binary_search(int beg, int bottom, const vector<int>& v) { int left = beg, right = v.size() - 1; while (left < right) { int mid = (left + right) / 2; if (v[mid] - v[beg - 1] >= bottom) { right = mid; } else { left = mid + 1; } } return { { beg,right },v[right] - v[beg - 1] }; } int main() { int N, M; int min; vector<int> vec_sum; vector<pair<int, int>> res; cin >> N >> M; vec_sum.resize(N + 1); for (size_t i = 1; i <= N; i++) { scanf("%d", &vec_sum[i]); vec_sum[i] += vec_sum[i - 1]; } min = vec_sum[N]; for (size_t i = 1; i <= N; i++) { auto fback = Binary_search(i, M, vec_sum); if (fback.second >= M) { if (fback.second == min) { res.push_back(fback.first); } else if (fback.second < min) { min = fback.second; res.clear(); res.push_back(fback.first); } } } for (auto p : res) { printf("%d-%d\n", p.first, p.second); } system("pause"); return 0; }
PS
对于这种运行时间要求严格的题(本题为100ms),输入输出最好使用scanf和printf而不是cin和cout,同样的算法,即使是关闭了同步,cin、cout仍然可能超时