尺取法
POJ 3061 Subsequence || 尺取法
给定一个序列,求最短的连续子序列长度,满足其各项和大于等于S。没有则输出0。序列都为正数。
(I) O(nlogn) 做法:dp
(II) O(n) 做法:尺取法
如果一个区间其和大于等于S了,那么不需要在向后推进右端点了,因为其和也肯定大于等于S但长度更长。所以,当区间和小于S时右端点向右移动,和大于等于S时,左端点向右移动以进一步找到最短的区间,如果右端点移动到区间末尾其和还不大于等于S,结束区间的枚举。
若[i, j-1]区间和小于S,[i, j]区间和大于等于S,那么我们停止推进右端点,当前ans = min(ans, j - i + 1)。然后我们推进左端点,得到[i+1, j],该区间和可能满足也可能不满足大于等于S,由于[i, j-1]区间和小于S,那么[i+1, j-1]区间和一定小于S,因此我们此时只能继续推进右端点,而不可能退回右端点。
#include <cstdio> #include <algorithm> using namespace std; const int maxn = 1e5 + 3; int a[maxn]; int main() { int cas; scanf("%d", &cas); while(cas--) { int n, S; scanf("%d %d", &n, &S); for(int i = 1; i <= n; ++i) scanf("%d", &a[i]); int ans = n + 1;//ans先置为最大 int l = 1, r = 1, sum = 0; while(1) { while(r <= n && sum < S) sum += a[r++];//[l, r)满足sum<S //if(r == n + 1) break; 不能写成这样,因为此时sum可能仍<S,这是满足条件的,要更新ans的。也因为此时左端点不一定为1。 if(sum < S) break;//此时r一定等于n+1。我们想break的是n个数之和仍<S,无满足条件的情况。 ans = min(ans, r - l); sum -= a[l++]; } if(ans == n + 1) ans = 0;//对应上面的break printf("%d\n", ans); } return 0; }
poj 3320
#include <cstdio> #include <vector> #include <queue> #include <cstring> #include <set> #include <map> using namespace std; const int maxn = 1e6 + 5; const int INF = 0x3f3f3f3f; int a[maxn]; map <int, int> cnt; set <int> t; int p, ans = INF; int main() { scanf("%d", &p); for(int i = 0; i < p; ++i) scanf("%d", &a[i]), t.insert(a[i]); int num = t.size(), tot = 0, l = 0, r = 0; while (1){ while(r < p && tot < num) if(cnt[a[r++]]++ == 0) tot++; if(tot < num) break; ans = min(ans, r - l); if(--cnt[a[l++]] == 0) tot--; } printf("%d\n", ans); return 0; }