《挑战程序设计竞赛》3.2.1 常用技巧-尺取法 POJ3061 3320 2566 2739 2100(1)

POJ3061

http://poj.org/problem?id=3061

题意

给定长度为n的整数数列以及整数S,求出总和不小于S的连续子序列的长度的最小值,如果解 不存在,输出0.

思路

如果用二分法
先求出sum[i],从第1个数到第i个数的区间和,每次固定一个开始查找的起点sum[i], 然后采用二分查找找到 sum[i] + S 的位置,区间长度即为(末位置-(起始位置-1)),用ans保存过程中区间的最小值。时间复杂度 O(nlogn)。
但如果用尺取法会将复杂度大大降低:
反复地推进区间的开头和末尾,来求满足条件的最小区间的方法称为尺取法。
主要思想为:当a1, a2 , a3 满足和>=S,得到一个区间长度3,那么去掉开头a1, 剩下 a2,a3,判断是否满足>=S,如果满足,那么区间长度更新,如果不满足,那么尾部向后拓展,判断a2,a3,a4是否满足条件。重复这样的操作。
时间复杂度 O(n)。
尺取法的深入理解:
当一个区间满足条件时,那么去掉区间开头第一个数,得到新区间,判断新区间是否满足条件,如果不满足条件,那么区间末尾向后扩展,直到满足条件为之,这样就得到了许多满足条件的区间,再根据题意要求什么,就可以在这些区间中进行选择,比如区间最长,区间最短什么的。

代码

Source Code

Problem: 3061       User: liangrx06
Memory: 632K        Time: 63MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1e5;

int main(void)
{
    int t, n, s;
    int a[N];

    cin >> t;
    while (t--) {
        cin >> n >> s;
        for (int i = 0; i < n; i ++)
            scanf("%d", &a[i]);
        int ans = n+1;
        int l = 0, r = 0, sum = 0;
        while (true) {
            while (sum < s && r < n)
                sum += a[r++];
            if (sum < s) break;
            ans = min(ans, r-l);
            sum -= a[l++];
        }
        printf("%d\n", ans%(n+1));
    }

    return 0;
}

POJ3320

http://poj.org/problem?id=3320

题意

某人读一本书,要看完所有的知识点,这本书共有P页,第i页恰好有一个知识点ai,(每一个知识点都有一个整数编号)。全书同一个知识点可能会被提到多次,他希望阅读其中一些连续的页把所有知识点都读到,给定每页所读到的知识点,求最少的阅读页数。

思路

和上一题一样,也是尺取法的应用。
假设从某一页s开始阅读,为了覆盖所有的知识点读到t页,这样的话如果从s+1开始阅读,那么必须读到t’>=t位置,故可以用尺取法。
预处理时要用set来统计所有知识点。
尺取法循环过程中,对每个知识点用map来统计次数,增加后一项要把对应的知识点的编号次数+1,取出前一项要把对应的知识点的编号次数-1。用cnt统计覆盖的知识点数目,知识点次数由0变为1时cnt++,反之则cnt–。

代码

Source Code

Problem: 3320       User: liangrx06
Memory: 5116K       Time: 532MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
#include <map>
using namespace std;

const int N = 1e6;

int main(void)
{
    int n;
    int a[N];
    set <int> ideas;
    map <int, int> num;

    cin >> n;
    for (int i = 0; i < n; i ++) {
        scanf("%d", &a[i]);
        ideas.insert(a[i]);
        num[a[i]] = 0;
    }

    int s = ideas.size();
    int l = 0, r = 0, cnt = 0;
    int ans = n;
    while (true) {
        while (r < n && cnt < s) {
            if (num[a[r]] == 0) cnt++;
            num[a[r++]] ++;
        }
        if (cnt < s) break;
        ans = min(ans, r-l);
        if (num[a[l]] == 1) cnt--;
        num[a[l++]] --;
    }
    printf("%d\n", ans);

    return 0;
}

POJ2739

http://poj.org/problem?id=2739

题意

给定一个10000以内的数字,判断这个数字是否可以由几个连续的素数(例如:2,3,5,7…)相加得到,并且给出这个数可以有几组这样的解。
输入:每行一个数字,0为退出
输出:每行一个数字,对应输入的每个数字的解的组数。

思路

预先用素数筛法求出10000以内的素数,按顺序存储。然后用尺取法求解。

代码

Source Code

Problem: 2739       User: liangrx06
Memory: 284K        Time: 0MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 10000;

bool isPrime[N+1];
int prime[N+1];

void initPrime()
{
    fill(isPrime, isPrime+N, true);
    fill(prime, prime+N, N+1);
    isPrime[1] = false;
    int cnt = 0;
    for (int i = 2; i <= N; i ++) {
        if (isPrime[i]) {
            prime[cnt++] = i;
            if (i > (int)sqrt((double)N)) continue;
            for (int j = i*i; j <= N; j += i) {
                isPrime[j] = false;
            }
        }
    }
}

int main(void)
{
    int n;

    initPrime();
    while (cin >> n && n) {
        int ans = 0;
        int l = 0, r = 0, sum = 0;
        while (true) {
            while (sum < n && prime[r] <= n)
                sum += prime[r++];
            if (sum < n) break;
            if (sum == n) ans++;
            sum -= prime[l++];
        }
        printf("%d\n", ans);
    }

    return 0;
}

POJ2100

http://poj.org/problem?id=2100

题意

是否存在一段连续的数, 平方相加等于n, 求出方案数并输出方案.

思路

连续子序列的性质通常用尺取法比较合适。另外这个题其实保存序列后不需要重新排序,顺序输出即可。

代码

Source Code

Problem: 2739       User: liangrx06
Memory: 284K        Time: 0MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 10000;

bool isPrime[N+1];
int prime[N+1];

void initPrime()
{
    fill(isPrime, isPrime+N, true);
    fill(prime, prime+N, N+1);
    isPrime[1] = false;
    int cnt = 0;
    for (int i = 2; i <= N; i ++) {
        if (isPrime[i]) {
            prime[cnt++] = i;
            if (i > (int)sqrt((double)N)) continue;
            for (int j = i*i; j <= N; j += i) {
                isPrime[j] = false;
            }
        }
    }
}

int main(void)
{
    int n;

    initPrime();
    while (cin >> n && n) {
        int ans = 0;
        int l = 0, r = 0, sum = 0;
        while (true) {
            while (sum < n && prime[r] <= n)
                sum += prime[r++];
            if (sum < n) break;
            if (sum == n) ans++;
            sum -= prime[l++];
        }
        printf("%d\n", ans);
    }

    return 0;
}

你可能感兴趣的:(poj,尺取法,挑战程序设计竞赛)