尺取法之前在挑战一直有看到,印象中尺取法是在一个线性数组上,用两个变量表示两个下标,作为选取区间的左右端点,再根据实际情况来不断推进左右端点得出答案。有时候看到题目的数据规模在十万级甚至百万级的时候,使用两个嵌套for循环来枚举所有情况的话是会超时的,但是尺取法的复杂度是O(n)级的,所以尺取法是一个对于遍历方法来说更加优化的一个算法。
但是一道题能不能使用尺取法也是有条件的:即选取区间时具有一定的规律,或者说我们可以很明显地看出选取区间过程中结果的变化趋势,所以在对我们已经选取的区间进行判断之后,我们就可以知道下一步该怎么做,从而一步一步推进我们的区间,最后得出答案。
反之,如果对已经选取的区间,无法确定解决问题的下一步,那么就不能用尺取法,所以在遇到数据规模比较大的时候,可以验证一下问题是否满足这一种特性,如果满足了,就可以使用尺取法来求解问题了。

下面是尺取法的几道例题:

Subsequence POJ - 3061

问题:
A sequence of N positive integers (10 < N < 100 000), each of them less than or equal 10000, and a positive integer S (S < 100 000 000) are given. Write a program to find the minimal length of the subsequence of consecutive elements of the sequence, the sum of which is greater than or equal to S.
Input
The first line is the number of test cases. For each test case the program has to read the numbers N and S, separated by an interval, from the first line. The numbers of the sequence are given in the second line of the test case, separated by intervals. The input will finish with the end of file.
Output
For each the case the program has to print the result on separate line of the output file.if no answer, print 0.
Sample Input
2
10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5
Sample Output
2
3

题意:有一个序列,还有一个数s,求这个序列中最小长度的连续区间长度,这个区间满足其中的元素之和大于等于s。

思路:定义两个变量start和end,一开始都放在第一个元素,然后让end先右移,当end右移到区间和大于s的时候先停一下,记录这次区间长度,然后start右移一位,再进入以上的循环,直至区间和小于s停止。

代码:

#include <iostream>
#include <math.h>
using namespace std;
typedef long long int ll;

ll a[100010];
ll sum,n,s;
int ans;
int main(){
    int test;
    scanf("%d",&test);
    while(test--){
        scanf("%d %I64d",&n,&s);

        for(int i=0;i<n;i++){
            scanf("%I64d",&a[i]);
        }
        int start=0,end=0;
        ans = 0x3f3f3f3f;
        sum = 0;

        while(true){
            while(end<n && sum<s) 
                sum+=a[end++];
            if(sum < s)
                break;
            ans = min(ans,end-start);
            sum -= a[start++];
        }
        if(ans == 0x3f3f3f3f) ans=0;
        printf("%d\n",ans);
    }
    return 0;

}

Sum of Consecutive Prime Numbers POJ - 2739

题目:
Some positive integers can be represented by a sum of one or more consecutive prime numbers. How many such representations does a given positive integer have? For example, the integer 53 has two representations 5 + 7 + 11 + 13 + 17 and 53. The integer 41 has three representations 2+3+5+7+11+13, 11+13+17, and 41. The integer 3 has only one representation, which is 3. The integer 20 has no such representations. Note that summands must be consecutive prime
numbers, so neither 7 + 13 nor 3 + 5 + 5 + 7 is a valid representation for the integer 20.
Your mission is to write a program that reports the number of representations for the given positive integer.
Input
The input is a sequence of positive integers each in a separate line. The integers are between 2 and 10 000, inclusive. The end of the input is indicated by a zero.
Output
The output should be composed of lines each corresponding to an input line except the last zero. An output line includes the number of representations for the input integer as the sum of one or more consecutive prime numbers. No other characters should be inserted in the output.
Sample Input
2
3
17
41
20
666
12
53
0
Sample Output
1
1
2
3
0
0
1
2

题意:有一个序列,他的元素是2到10000的质数,给出一个值s,在这个序列中,若有一段连续区间和等于s,就称为s的代表,问在这个序列中,有几个s的代表。

思路:其实思路和上题差不多,也是两个端点蠕动,sum表示当前区间和,若区间和等于s时,计数器加一,加一的同时sum要减掉端点表示的质数,区间和大于等于s时也意味着左端点要向右移一位了,然后计算区间和,终止条件即是最右端点的数大于s(因为这时候就再往后加不可能得到答案了)。

代码:

#include <iostream>


using namespace std;

int prime[2000];

bool isprime(int n){
    bool flag=1;
    if(n==2){
        return true;
    }
    for(int i=2;i*i<=n;i++){
        if(n%i==0){
            flag=0;
            break;
        }
    }
    return flag;
}

void getprime(){
    int index=0;
    for(int i=2;i<=10000;i++){
        if(isprime(i)){
            prime[index++] = i;
        }
    }
}

int main(){

    int n;
    getprime();

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

Jessica’s Reading Problem POJ - 3320

题目:
Jessica’s a very lovely girl wooed by lots of boys. Recently she has a problem. The final exam is coming, yet she has spent little time on it. If she wants to pass it, she has to master all ideas included in a very thick text book. The author of that text book, like other authors, is extremely fussy about the ideas, thus some ideas are covered more than once. Jessica think if she managed to read each idea at least once, she can pass the exam. She decides to read only one contiguous part of the book which contains all ideas covered by the entire book. And of course, the sub-book should be as thin as possible.

A very hard-working boy had manually indexed for her each page of Jessica’s text-book with what idea each page is about and thus made a big progress for his courtship. Here you come in to save your skin: given the index, help Jessica decide which contiguous part she should read. For convenience, each idea has been coded with an ID, which is a non-negative integer.

Input
The first line of input is an integer P (1 ≤ P ≤ 1000000), which is the number of pages of Jessica’s text-book. The second line contains P non-negative integers describing what idea each page is about. The first integer is what the first page is about, the second integer is what the second page is about, and so on. You may assume all integers that appear can fit well in the signed 32-bit integer type.

Output
Output one line: the number of pages of the shortest contiguous part of the book which contains all ideals covered in the book.

Sample Input
5
1 8 8 8 1
Sample Output
2

题意:每页书都有一个知识点,求一个最小的连续的区间,这个区间可以看完所有知识点。

思路:这道题也是可以用尺取法的,先确定一个可以看完所有点的区间,记录本次长度,然后左端点右移,再确定是否可以看完,直至左端点右移到本区间不可以看完所有知识点为止,这道题难点是怎么知道有几个知识点,和怎么判断这次是不是看了这个知识点,在这里可以用一个set和一个map来解决问题。

代码:

#include <iostream>
#include <set>
#include <map>
using namespace std;

set<int> setOfKnowledge;
map<int,int> mm;
int page[1000010];

int sum=0;
int start=0,end=0;
int ans=0x3f3f3f3f;

int main(){
    int pageNumber;


    scanf("%d",&pageNumber);

    for(int i=0;i<pageNumber;i++){

        scanf("%d",&page[i]);
        setOfKnowledge.insert(page[i]);

    } 

    int num = setOfKnowledge.size();

    while(true){

        while(end<pageNumber && sum<num){

            if(mm[page[end++]]++ == 0){
                sum++;
            }

        }

        if(sum<num)  break;

        ans = min(ans, end-start);

        if(--mm[page[start++]] == 0)
            sum--;

    }
    printf("%d\n",ans);
    return 0;
}

引用:ACM常用的解题技巧:尺取法

更多推荐

对尺取法的一些理解