题目大意:
给定长度为n的数列整数 a0,a2,...,an−1 以及整数S 。求出总和不小于S的连续子序列的长度的最小值。如果解不存在,则输出0。
限制条件:
10<n<105
0<ai≤104
S<108
输入
第一行, T, 表示测试数据组数。
接下来T组测试数据,每组2行。
每组测试数据第一行为N和S,第二行为N个数,即 a0,...an−1
输出
对于每组测试数据,输出最短的满足条件的序列长度, 无法满足则输出0
样例输入
2
10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5
样例输出
2
3
题解:
看到题目第一时间想到刚学的二分法,从1到N 二分查找最小的满足条件的序列长度。
每次判断是否满足,时间复杂度为 O(n) ,所以该算法的复杂度为 O(nlog(n))
代码:
#include <iostream>
#define MAXN 100010
using namespace std;
int a[MAXN], N, S;
bool is_ok(int m) {
int sum = 0;
bool flag = false;
for (int i = 0; i < m; i++)
sum += a[i];
if (sum >= S) return true;
if (!flag) {
for (int i = m; i < N; i++) {
sum -= a[i-m];
sum += a[i];
if (sum >= S) {
flag = true;
break;
}
}
}
return flag;
}
int solve() {
int l = 1, r = N;
int sum = 0;
for (int i = 0; i < N; i++)
sum += a[i];
if (sum < S) return 0;
while (l < r) {
int mid = (l+r)/2;
if (is_ok(mid)) r = mid;
else l = mid+1;
}
return r;
}
int main() {
int T;
ios::sync_with_stdio(false);
cin >> T;
for (int t = 0; t < T; t++) {
cin >> N >> S;
for (int i = 0; i < N; i++)
cin >> a[i];
cout << solve() << endl;
}
return 0;
}
然而,这题还有时间复杂度更低的算法,正是《挑战程序设计竞赛》3.2小节介绍的 “尺取法”:
以
as
开始总和最初大于S时的连续子序列为
as+...+at−1
,这时
as+1+...+at−2<as+...+at−2
所以从
as+1
开始总和最初超过S的连续子序列如果是
as+1+...+at′−1
的话,则必然有
t≤t′
。利用这一性质便可以设计出如下算法:
(1) 以
s=t=sum=0
初始化。
(2) 只要依然有
sum<S
,就不断将
sum
增加
at
,并将
t
增加1。
(3) 如果(2) 中无法满足
(4) 将
sum
减去
as
,
s
增加1然后返回(2)
对于这个算法, 因为
代码:
#include <iostream>
#define MAXN 100010
using namespace std;
int a[MAXN], N, S;
int solve() {
int res = N + 1;
int s = 0, t = 0, sum = 0;
for (;;) {
while (t < N && sum < S) {
sum += a[t++];
}
if (sum < S) break;
res = min(res, t - s);
sum -= a[s++];
}
if (res > N)
res = 0;
return res;
}
int main() {
int T;
ios::sync_with_stdio(false);
cin >> T;
for (int t = 0; t < T; t++) {
cin >> N >> S;
for (int i = 0; i < N; i++)
cin >> a[i];
cout << solve() << endl;
}
return 0;
}