笔记_常见优化技巧1

方格图中的矩形

题目的模式常见于,在一片由小方格组成的巨大的方格图中,找出某种要求的矩形。

由于巨大的“长”与“宽”的范围,以及由方格组成矩形的递归性,矩形的位置性,某些格子禁用的制约性,题目的难点多在于较高的枚举量带来的时间复杂度表示矩形的方法数矩形个数的策略递归策略的发掘

正方形矩阵递归

当题目中给出的矩形是明确的正方形,且其边长长度明确为2^n时,它很可能就要在正方形个体包含全部的特点上去做文章了。

任何一个边长为2^{n}的大正方形在格子中都能被拆分成4个边长为2^{n-1}的小正方形,这也意味着,只要题目的问题是具体到单个格子上的某些要求,不论它给出的初始图形有多大,最后都等效于在一个四格正方形中的单区域问题,而对于从大正方形到小正方形的过程,也一样是一个大正方形的单区域问题。

把一个格子的问题化成一个大的区域,把一个大区域的问题不断递归到一个格子,由小化大,由大化小,这就是2^n边长正方形的解题策略。

ps:此类题目在递归的过程中可能方向有点多,初始分割正方形就要四个方向,每次分割之后如果在小正方形中还需要额外的判断,则单次递归的方向就会达到4n个,但是这依旧是很好的解题策略,耐心把情况清楚就可以了。

模板题:

任意矩形找其内的小矩形

在网格图中,大矩形一样是由小矩形构成的,但是,相较于特殊的正方矩形,普通的矩形的大小构成并没有特别的规律,只能采取淳朴的计数法去找矩形。

所以,在这种题目中,能做文章的地方不是矩形,而是有关矩形的计数与判定

淳朴——找任意矩形内所有矩形的个数

考察矩形的计数与表示法。

给你一个网格图,你该如何快速求出图中所有正方形与长方形的个数呢?

在一个网格中,决定单个矩形的是什么?是什么将它们和其他矩形区别开来?

  • 顶点位置

  • 长宽的值

没错,这些确实决定了单个矩形,而且是最直观、最容易想到的方法。

但是在经过尝试后会发现,采用这种思想去统计各类矩形的个数相当麻烦,矩形上最多的就是“位置不同的点了”;麻烦,易错,耗时还长。

可是这类题就是根据“决定矩形的事物”去找矩形呀,那还有什么其他表示方法吗?

——当然有。

  • 长宽的值

  • 长宽的位置

把二维的网格图看成两个一维的轴长与宽的所有位置便是它们所在线段的所有子线段(个数为n!,其中n是线段总长),把长可能取的值与宽可能取的值相乘,便是矩形所有可能的构成了。

说白了,在一个网格图上矩形的位置的表示,本质上就是被赋予了位置的长和宽

技巧——矩形特质的寻找与表示

悬线法,矩形的找寻

确定(枚举)矩形的高度,探寻在这个位置、这个高度下,它所能向左右延伸的最大值。

通常存储:悬线长、悬线所能到达的最左列,悬线所能到达的最右列。

特点:

  • 通过特定位置的点,记录矩形的长和宽。这种方法能够完全不重不漏的把一个矩形所有的信息浓缩到一个格子里,且每一个“最大”矩形所占据的格子里,一定有一个格子能通过悬线法表示出这个矩形的信息

  • 它通过从网格图从上至下的递推,层层确定(枚举)悬线的长度和左右能达到的范围。其中,左右能达到的范围,都以和上一行相比的最小范围为主。

  • 其中,特定位置的点只是为了确保所找矩形的不重不漏、面面俱到,并不能提供具体定位的效果。若需要确定具体位置,还需要存储:该点的悬线顶端所处的那一行

例题:

山形法,以底格为重

例:

要统计图示中,不含黑色地块的所有矩形的个数,应该采用什么方法?

从上至下,以行为基础向下枚举,找出在以x行为底的情况下,枚举包含x行上某一格的所有矩形的数量。

从上至下设定底,保证了在讨论包含特定格子的所有矩形时格子的不重不漏。

例:假设目前已经讨论到了第4行;

如图:

那么应该统计以第4行为底、枚举包含第4行上一格的所有所有矩形数量

即:

如图所示,以某一格为往上为“悬线”,分别向左右(实际上为避免重复统计,向左右延展的条件不同)扩展至高度小于(小于等于)当前“悬线”高度,然后再对这个扩展后的矩形进行矩形个数的“计数”。

这种算法更像是悬线法的变体。实际上,还是找出以某行为底的小图中的悬线,再以悬线的高度为限制加以计数。

其中所用到的计数原理与我的直觉有些不同,悬线左右个数的排列组合,都是从“0”开始的。

例如:包含目标格的所有底边数 = l*r,其中l范围为0左侧的方格数,r范围为0右侧的方格数,故相乘的实际值实为(左侧的方格数+1)(右侧的方格数+1),即包含某侧取0的情况。

同时,在底边进行这种枚举的同时,还需要注意左右测的大于等于小于等于,的边界情况,因为枚举的边界相邻,左右区间需要半开半闭以避免重复。

该方法,本质上是把在二维网格数矩形的问题,分化简化成了一个个类似的在一维网格数矩形的问题。

例如,如下一维网格,其中包含1的所有矩形数为

(1+1)*(3+1)或(2-0)*(6-2)

例题:

变化区间内的最值

关键:

  • 区间长度一定

  • 区间在变化

  • 区间的最值

区间所包含的数据中,在最值易主前就被甩出区间的数据是不必存储的。

只需存储区间最值易主后,所有还在区间中的、具备争霸条件的数据。

对应数据结构:单调队列。

单调队列的实现与功能即是对上述思想的模拟。

例题:

以某值为最的最大区间

关键:

  • 区间延伸的方向(需单一确定)

  • 区间的最值

区间内存储的都是当前区间内最大的、第二大的、第三大的……其中,新加入的数将移除记录中所有比它小的数。

在区间延伸方向一定的情况下,这样的操作能够让区间内的最值有序的记录“相邻两座高峰间较小山峰的数量”并迭代。

同时,这一个过程可分解,即可递归。

对应数据结构:单调栈。

单调栈的实现与功能即是对上述思想的模拟。

例题:

不定区间的最值——ST表

单调队列由其区间变化的规律性决定了它可以排队——

一个王朝随着时间顺序流逝,下一个王的可能人选只能是寿命比当朝皇帝长的人

而如果失去时间顺序呢?

在一个大集合中,频繁的选取任意子集,要求其中每个子集的王,该怎么办?

首先,明显可以发觉,倘若是大量选取子集,虽说是任意选取,但后选的子集还是能够根据先前求出的区间“王”来拼凑出自己的“王”。

那么现在需要解决的就是,找到一个确切且合理的区间划分规则,使所有初始区间都能快速完成初始化,使所有区间都能通过初始区间快速、方便的拼凑出来。

而ST表就提供了一个方便快捷的方式。

在时间的优化上,它提出了“以指数增长的区间长度”为初始区间,单点初始化的时间复杂度变为O(lnN);

在拼凑方面的优化上,它的区间增长长度采用2^n的形式,使任意一个区间( l , r )都可以由它内部的分别以“ l ”和“ r ”为端点的两个小区间拼凑出来,时间复杂度仅为O(1) 。

所以,ST表的本质就是:它确定了一个方便合理的区间划分规则,明确了区间的存储和取用方式,明确了区间与区间间的关系,充分寻找与利用每个阶段的“王”去寻找新阶段的“王”。

int ST_query(int l,int r){//询问l~r区间的最值
    int Lg=Log[r-l+1];//计算l~r之间长度对应的log2值
    return max(stMax[l][Lg],stMax[r-(1<<Lg)+1][Lg]); 
}

void ST_prework(){//ST表预处理
    for(int i=1;i<=n;i++){//f[i][j]=从i开始,长为2^j区间内的最值
        f[i][0]=a[i];//长为1时的最值
    }
    for(int j=1;j<=Log[n];j++){//根据最长区间长度log[n]进行遍历
        for(int i=1;i+(1<<j)-1<=n;i++){//遍历开始位置i
            f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);//左边的最值与右边最值中较大者为整个区域的最值
        }
    }
}
  • 首先,确定区间长度,遍历区间的开头。因为总区间长度过长,故用对数的形式处理选区间长度这一操作。区间长度皆选用2的次方。

  • 第二步,赋初值。从这里开始,区间长度选用2的n次方的优势就显现出来了。任何一个j<2^n的区间[i,j]都可以被分成[ i , 2^{n-1}]与[ i+2^{n-1} , j ]两个区间去处理。故在初始化时,你可以很明确的看到,它是先初始化区间的长度枚举区间的开头,因为在递推初始化时,它是需要上一个区间长度、任意的区间开头。

  • 例题:

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值