尺取法

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

 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);
} 
原文地址:https://www.cnblogs.com/astonc/p/10846719.html