尺取法

 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;
}

 

posted @ 2020-10-23 09:32  .Ivorelectra  阅读(99)  评论(0编辑  收藏  举报