• 尺取法


      尺取法通常是指对数组保存一对下标(起点,终点),然后根据实际情况交替推进两个端点直到得出答案的方法。

     1.

     

      

      由于所有的元素都大于零,如果子序列 [s, t] 满足 as + .... at ≥ S,那么对于任何的 t < t' 一定有 as + .... at‘-1 ≥ S。

    此外对于区间[ s, t]上的总和来说,如果令 sum(i)= a0 + .... + ai - 1,那么  as + .... at - 1 = sum(t)- sum(s)。

      因此预先以O(n)的时间计算好 sum 的话,就可以以O(1)的时间计算区间上的总和。这样的haul,子序列的起点 s 确定以后,便可以用二分搜索快速地确定使序列和不小于 S 的结尾的最小值。

    int n, S;
    int a[MAX_N];
    
    int sum[MAX_N + 1];
    
    void solve() {
        for (int i = 0; i < n; i++)
            sum[i + 1] = sum[i] + a[i];
        if (sum[n] < S) {
            printf("0
    ");
            return;
        }
        int res = n;
        for (int s = 0, sum[s] + S <= sum[n]; s++) {
            int t = lower_bound(sum + s, sum + n, sum[s] + S) - sum;
            res = min(res, t - s);
        }
        printf("%d
    ", res);
    }

      复杂度为O(n log n)。但我们还可以更加高效地来求解。

      设以 ai 开始总和最初大于 S 时的连续子序列为 as + .. +at ,这时 as+1 + ... + at - 2 < as +...+ at-2 < S

      所以从 as+1 开始总和最初超过 S 的连续子序列如果是 as + .. +at‘ +1 的话,则必然有 t ≤ t'。利用这一特性便可以设计出如下算法:

      (1)以 s = t = sum = 0 初始化;

      (2)只要依然有 sum < S,就不断将 sum 增加 at ,并将 t 增加 1;

      (3)如果(2)中无法满足 sum ≥ S 则终止。否则的话,更新 res = min(res, t - s);

      (4)将 sum 减去 as,s 增加 1 然后回到(2)。

      对于这个算法,因为 t 最多变化 n 次,因此只需O(n)的复杂度就可以求解这个问题。

    void solve() {
        int res = n + 1;
        int s = 0, t = 0, sum = 0;
        for (;;) {
            while (t < n && sum < S)
                sum += a[t++];
            if (sum < S) break;
            res = min(res, t - s);
            sum -= a[s++];
        }
        if (res > n) res = 0; //解不存在 
        printf("%d
    ", res);
    }

      

    像这样反复地推进区间的开头和末尾,来求取满足条件的最小区间的方法称为尺取法。

     2.

      

      

      我们假设从某一页 s 开始阅读,为了覆盖所有的知识点需要阅读到 t。这样的话可以知道如果从 s + 1 开始阅读的话,那么必须阅读到 t' ≥ t 页为止。由此这题也可以使用尺取法。

      在某个区间[ s, t ] 已经覆盖了所有的知识点的情况下,下一个区间 [ s + 1, t' ] (t' ≥ t)要如何求出呢?

      所有的知识点都被覆盖 等价于 每个知识点出现的次数都不小于 1

      由以上的等价关系,我们可以用二叉树等数据结构来存储 [ s, t ]区间上每个知识点的出现次数,这样把最开头的页 s 去除后便可以判断 [ s + 1, t ] 是否满足条件。

      从区间的最开头把 s 取出之后,页 s 上书写的知识点的出现次数就要减一,如果此时这个知识点的出现次数为 0 了,在同一个知识点再次出现前,不停将区间末尾向后推进即可。每次在区间末尾追加页 t 上的知识点的出现次数加 1,这样就完成了下一个区间上各个知识点出现次数的更新,通过重复这一操作可以以O(P log P)的复杂度求出最小的区间。

    int p;
    int a[MAX_P];
    
    void solve() {
        // 计算全部知识点的总数
        set<int> all;
        for (int i = 0; i < P; i++)
            all.insert(a[i]);
        int n = all.size();
        
        int s = 0, t = 0, num = 0;
        map<int, int> count;
        int res = P;
        for(;;) {
            while (t < P && num < n) 
                if (count[a[t++]]++ == 0) // 出现新的知识点 
                    num++;
            if (num < n) break;
            res = min(res, t - s);
            if (--count[a[s++]] == 0)  //某个知识点的出现次数为 0 了 
                num--;
        } 
        printf("%d
    ", res);
    } 
  • 相关阅读:
    Light oj 1082 Array Queries(区间最小值)
    Codeforces Round #179 (Div. 2)A、B、C、D
    poj 1976 A Mini Locomotive(01背包)
    Codeforces Round #178 (Div. 2)
    hackerrank challenges median
    poj 1961 Period(kmp最短循环节)
    poj 2182 Lost Cows(树状数组)
    ZOJ1117 POJ1521 HDU1053 Huffman编码
    poj 2352 Stars 树状数组
    这可能是最适合萌新入门Web安全的路线规划
  • 原文地址:https://www.cnblogs.com/astonc/p/10846719.html
Copyright © 2020-2023  润新知