• 尺取法总结


    尺取法

      顾名思义,像尺子一样取一段。尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。之所以需要掌握这个技巧,是因为尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以尺取法是一种高效的枚举区间的方法,一般用于求取有一定限制的区间个数或最短的区间等等。当然任何技巧都存在其不足的地方,有些情况下尺取法不可行,无法得出正确答案。
    使用尺取法时应清楚以下四点:

    1.什么情况下能使用尺取法
    2.何时推进区间的端点
    3.如何推进区间的端点
    4.何时结束区间的枚举

      尺取法通常适用于选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情况,通俗地说,在对所选取区间进行判断之后,我们可以明确如何进一步有方向地推进区间端点以求解满足条件的区间,如果已经判断了目前所选取的区间,但却无法确定所要求解的区间如何进一步得到根据其端点得到,那么尺取法便是不可行的。首先,明确题目所需要求解的量之后,区间左右端点一般从最整个数组的起点开始,之后判断区间是否符合条件在根据实际情况变化区间的端点求解答案。

    例:好吃的巧克力

    超市正在特价售卖巧克力,正好被贪吃的Lucky_dog看见了。
    巧克力从左到右排成一排,一共有N个,M种。
    超市有一个很奇怪的规定,就是你在购买巧克力时必须提供两个数字a和b,代表你要购买第 a 个至第 b 个巧克力(包含 a 和 b)之间的所有巧克力。
    假设所有巧克力的单价均为1元。Lucky_dog想吃所有种类的巧克力,但又想省钱。作为Lucky_dog的朋友,他请你来帮他决定如何选择购买巧克力时的 a 和 b。

    输入格式

    第一行包含两个正整数 N 和 M(M<=N, N<=10^6 , M<=2000),分别代表巧克力的总数及种类数。
    第二行包含 N 个整数,这些整数均在1 至 M 之间,代表对应的巧克力所属的种类。

    输出格式

    输出仅一行,包含两个整数a和 b(a<=b) ,由一个空格隔开,表示花费最少且包含所有种类巧克力的购买区间。
    数据保证有解,如果存在多个符合条件的购买区间,输出a最小的那个。

    输入样例

    12 5
    2 5 3 1 3 2 4 1 1 5 4 3

    输出样例

    2 7

    Part1 题目分析

      这一题我刚开始的想法比较暴力直接,就是通过三重循环将从每一个位置开始在宽度范围内的情况都遍历一遍然后找出其中区间最短并且最靠前的那一组。但是由于范围在1E6之内,这种方法只拿了三个点其余点都超时。
      由于该题数据范围较大,我们需要一种更加巧妙的遍历方式既能符合题意又能高效运行不超时。通过回想上一种方法,我们可以发现,上一种方法在遍历过程中其实存在很多重复且不必要的步骤导致运行速度变慢。
      我们其实没有必要反复的进行遍历,只要在满足条件的最小限度进行遍历就行了。所以可以定义一个变量来统计扫过元素的种类,两个变量分别统计上下区间。在遍历的过程中当种类已经齐全时就停止循环,再与之前遍历的长度作比较,如果长度更短就更新上下区间。然后将上一次扫过的第一个元素在统计个数的数组中删去,再继续向下遍历,最终得出最短且靠前的区间。

    Part2 完整代码

    #include<iostream>
    #include<cstring>
    using namespace std;
    int main(){
    	int n,m;
    	cin>>n>>m;
    	int a[n+1];
    	for(int i=1;i<=n;i++) cin>>a[i];
    	int num[m+1],left=1,right=1,ansl=0,ansr=1000000,kind=0;
    	memset(num,0,sizeof(num));
    	while(1){
    		while(kind<m&&right<=n){//当种类数齐全或遍历到最后一位时结束循环
    			if(num[a[right]]==0) kind++;
    			num[a[right]]++;
    			right++;
    			
    		}
    		if(right==n+1) break;//遍历到最后一位结束循环
    		if(ansr-ansl>right-left){//更新最优区间
    			ansr=right;
    			ansl=left;
    		}
    		num[a[left]]--; //上一次遍历的区间右移一位
    		if(num[a[left]]==0) kind--;
    		left++;
    	}
    	cout<<ansl<<" "<<ansr-1;
    }
    

    PS.更多有关尺取法的例题和使用方法可见下面的链接

    ————————————————
    本文部分转载自https://blog.csdn.net/consciousman/article/details/52348439

  • 相关阅读:
    windows下区块链,私有链搭建详细教程(图文详解)
    MySQL 5.7 mysqldump的Bug导致复制异常
    关于MySQL 5.6 DDL阻塞DML的问题!
    mysqldump 根据时间字段导出数据的问题
    MySQL undo redo
    InnoDB undo, redo,binlog,data什么时候写?
    MySQL master 宕机导致slave数据比master多的case
    Jboss配置自动重连数据库
    星爷001正式开始写blog啦
    本地及远程二级缓存
  • 原文地址:https://www.cnblogs.com/beyondzones/p/12547167.html
Copyright © 2020-2023  润新知