题意 : 找出给定序列长度最小的子序列,子序列的和要求满足大于或者等于 S,如果存在则输出最小长度、否则输出 0(序列的元素都是大于 0 小于10000)
分析 : 有关子序列和的问题,都可以考虑采用先构造前缀和的方式来进行接下来的操作 ( 任意子序列的和都能由某两个前缀和的差表示 )。
二分做法 ==> 我们枚举起点,对于每一个起点 St 二分查找看看 St 后面有没有前缀和是大于或者等于 [ St的前缀和 ] + S 的,如果有说明从当前起点开始有一个终点使得起终之和是大于或者等于 S 的,在这个过程中维护最小长度即可!时间复杂度为 O(nlogn)
#include<bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int arr[maxn], sum[maxn]; int main(void) { int nCase; scanf("%d", &nCase); sum[0] = 0; while(nCase--){ int n, s; scanf("%d %d", &n, &s); for(int i=1; i<=n; i++){ scanf("%d", &arr[i]); sum[i] = sum[i-1] + arr[i]; } if(sum[n] < s){ puts("0"); continue; } int ans = n; for(int i=1; i<=n; i++){ int st = i - 1; int en = lower_bound(sum+st, sum+n, sum[st]+s) - sum; //printf("%d %d\n", st, en); if(sum[en] - sum[st] < s) continue; ans = min(ans, en - st); } printf("%d\n", ans); } return 0; }
尺取做法 ==> 如果有一个满足条件的子序列为 arr[st]...arr[en-1] >= S,那么肯定有 arr[st+1]...arr[en-2] < arr[st]...arr[en-2] < S,所以如果将 st+1 作为起点也有满足条件的序列且令其终点为en‘-1,那么必定有 en‘-1 >= en-1,那么也就是说当前枚举完 st 这个点,其贡献的答案是 (en-1)-st+1,现在我们考虑 st+1 即下一个起点的时候考虑的终点应该是要考虑大于或者等于 en-1 的点,故尺取是正确的解法。时间复杂度为O(n)
///尺取法 #include<bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int sum[maxn]; int main(void) { sum[0] = 0; int nCase; scanf("%d", &nCase); while(nCase--){ int n, s; scanf("%d %d", &n, &s); int tmp; for(int i=1; i<=n; i++) scanf("%d", &tmp), sum[i] = sum[i-1] + tmp;///统计前缀和 if(sum[n] < s){ puts("0"); continue; } int L, R, ans;///左指针、右指针 ans = n; L = R = 1; while(L <= R && R <= n){ if(sum[R] - sum[L] < s) R++; else{ ans = min(ans, R-L); L++; } } printf("%d\n", ans); } return 0; }