写在前面
这篇文章两年前就写的差不多了,说到底鼠鼠还是条懒狗,还没写完就一直烂在草稿里吃灰。
岁月不饶人啊,两年后回来看自己写的东西还不禁感慨,卧槽,以前的我怎么这么牛逼,感慨的是现在好像被磨平了,再也懒得玩梗,也再也没有之前的创造力和想象力了。
检验一个教程成不成功的办法就是小白能不能学明白,如今功力尽失的我照着自己的教程学习了一遍,发现两年前认为写小故事可以方便理解的想法还是太天真了,我的水平只能写的又臭又长,翻好久才能找到自己想学的干货,建议直接点击目录的找干货请跳到这里部分,
无聊的故事
在一个0和1组成的 (两年前这句话还没这么怪吧) ,充斥着剑与魔法的世界里,有坏事做尽的恶龙,有保卫王国的勇者,也有三天两头就被掳走的公主。 这里祥和安宁,夜幕降临时,在挂满灯笼的集市里踱步,听几声吆喝,颇有种大唐盛世的感觉。
*又是美好的一天,鸟儿在歌唱,花儿在绽放。*新手村里传来捷报,公主又双叒叕被抓走了。
就像西方不能失去耶路撒冷,这个世界也不能缺少勇者。阿李、阿黄、阿刘三人历经重重困难,终于达到了关押公主的城堡。
为了解救公主,他们必须打败城堡中的所有恶魔。每个勇者都会先战胜最弱的恶魔,提升自己的等级,从而更好地面对更强大的对手。。
“把所有恶魔全部欧拉一边就行了”, 阿李是一位狂战士,在他莽夫的策略路过的狗都得埃两巴掌,没想到转角就遇到战斗力最高的恶魔,至今下落不明。这种行为我们称之为暴力。
阿刘是个大聪明,他把城堡中每个区域最fw的恶魔都记到一个本子上,每次只要看一眼,就能找到战斗力最低的恶魔,结果繁杂的准备工作彻底击垮了这个成年人的心智。这种行为我们称之为打表。
阿黄是个魔法师,他拥有一面魔镜,进入城堡之前先对着镜子吟唱咒语“阿米诺斯!”,魔镜就飞起来了,以后只要对它说“一得个拉米!魔镜魔镜告诉我,城堡里这一块最弱的恶魔是谁”,镜子上就会出现这个范围里最弱的恶魔。毕竟是新手村买来的魔镜,如果有新的恶魔来了,镜子就白飞了
就像落叶忘不了秋风的温柔,多年后,阿黄成了这个世界数一数二的魔法师,荣归故里,衣锦还乡。他回到了那条灯火通明的街,和街坊邻居们吹嘘起自己的冒险之旅,让人仿佛感受到了那个时光的英勇。
”阿黄,给我们讲讲你当年救出公主那面镜子的故事吧…“
阿黄的镜子
Input
输入第一行:一个整数 n (0<n<1e5) 表示城堡中恶魔的数量
接下来一行:n个整数 a[i] 第i个恶魔的战斗力,(0<a[i]<100)
接下来一行:一个整数 t(0<t<1e5)代表询问的次数
接下来t行:两个整数L,R,请你告诉阿黄第L到R个恶魔中最低战斗力是多少 (0<L<R<=n)
Output
对于每次询问输出一行 ,一个整数表示区间内恶魔的最低战斗力
样例输入 :
5
5 4 3 2 1
2
1 5
3 4
样例输出:
1
2
俗话说得好,没有耕坏的地,只有累死的牛,由于敌人数量高达1e5,每次查询都像某狂战士那样暴力求解显然是不可取的。聪明的勇者一定发现了,阿黄镜子的秘密就在于st表
找干货请跳到这里
ST表及其实现
ST表(Sparse Table,稀疏表)是一种简单的数据结构,主要用来解决RMQ(Range Maximum/Minimum Query,区间最大/最小值查询)问题。
说人话就是st表可以帮我们查询某个区间内的最值
吟唱咒语:“阿米诺斯!”,就是复杂度O(nlogn)的预处理操作,这里采用了倍增思想:
利用stmin[i][j]数组储存从i号开始,长度为2j的区间中的最小的数,这一点十分重要
比如stmin[1][3]代表从1开始长度为23=8的区间,也就是1-8号的最小值
每次查询只要O(1)的复杂度就会给出答案,所以不会像阿刘那样耻辱下播。
现在,我们有了以任意一点为起点长度为2k区间的最小值
如果查询1-5的最小值怎么办?对于每次查询,显然区间长度不能保证是2k
stmin[1][2]长度为4, 查询的是1-4的最小值
stmin[1][3]长度为8, 查询的是1-8的最小值
所以我们要把这个区间一分为二,寻找一个最小的k满足从两边端点为起点,相向而行且长度为2^k的两个区间,使得这两个区间的并集正好是1-5,如图所示,当k=2时,这两个区间的最小值就是答案了。
这个k值也很好求,就是log2(区间长度) 向下取整,此时2k一定保证和区间长度贴的很近甚至相等
说人话就是找两个长度为2k的区间,一个在左一个在右而且这两个区间并起来是要求的区间,把最小值夹出来
用lg[i]数组储存以2为底i的对数 (向下取整),即:
lg[1] =0、lg[2] =1、lg[3] = 1、lg[4] = 2、lg[5] = 2 ……
设左端点为l,右端点为r,k值的计算公式就是k = lg[r-l+1]。(括号里是长度所以很好记)
区间 1最小值为stmin[l][k]
区间2 最小值为stmin[r-2k+1][k] (长度为2k且终点在r处,起点显而易见:r-2k+1)
阿黄的咒语难度就在stmin和lg这两个数组怎么获得
lg数组
因为是向下取整,我们只要默认后一项和前一项一样,遇到2的指数幂就加1
因为1也是2的指数幂(2^0),所以我们定义lg[0] = -1
-1,0,1,1,2,2,2,2,3,3,3,3……这样一个神奇的数列就出现了
怎么判断i是不是2的指数幂?
这时候就要请来神奇的位运算了,烫知识:只有i是2的指数幂的时候,(i&(i-1)) == 0这个等式才会成立
如图所示,分别展示i为2的指数幂
2,4,6和非2的指数幂3,5,7时的情况
先初始化lg[0] = -1,后面每一项都和前一项相等,逢2的指数幂加一 即:lg[i] = (i & (i - 1)) == 0 ? lg[i - 1] + 1 : lg[i - 1];
神奇!就像我这么神奇!
stmin数组
stmin[i][0]的值我们是上来就知道的,从i开始,长度为20 = 1区间内的最小值就是它本身。
有了这个stmin[i][0],想求出stmin[i][1]很容易,还记得怎么查询吗?,将想求的区间分成两个,求这两个区间的最小值较小的那个。
求stmin[i][1],肯定分成stmin[i][0]和stmin[i+1][0]了
如图所示,以一个长度为三的序列为例(中间黄色部分)
在上下两个半边分别用stmin[i][0]和stmin[i+1][0](绿色部分)
推出了st[i][1](蓝色部分)
区间长度为2j,将这个区间一分为二,就是两个长度为2 (j-1)的区间,正好能夹出想要的结果
有了stmin[i][1],我们就能推出stmin[i][2]
有了stmin[i][2]我们就能推出stmin[i]][3]
…………
以此类推,所以我们得到递推公式
stmin[i][j] = min(stmin[i][j - 1], stmin[i + 2^(j-1)][j - 1]);
理(借)论(口)存在,实(魔)践(法)开始
下面让我们瞧瞧神奇的阿黄在预处理的时候究竟施了什么魔法
void pre()
{
lg[0] = -1;
for (int i = 1; i <= n; i++)
{
lg[i] = (i & (i - 1)) == 0 ? lg[i - 1] + 1 : lg[i - 1];//预处理lg数组
stmin[i][0] = a[i];//预处理stmin[i][0],下一步由stmin[i][0]递推出stmin[i][1]
}
for (int j = 1; j <= lg[n]; j++)//j代表长度为2^j的区间,一共只有n个数,2^j<=n,即j<=lg[n]
{
for (int i = 1; i + (1 << j) - 1 <= n; i++)//(1<<j) 即 2^j
{
//根据已知的stmin[i][j-1]递推出stmin[i][j],所以j的循环要放在外圈
stmin[i][j] = min(stmin[i][j - 1], stmin[i + (1 << j-1)][j - 1]);
}
}
}
查询操作就更简单了
int getmin(int l, int r)
{
int k = lg[r - l + 1];
return min(stmin[l][k], stmin[r - (1 << k) + 1][k]);
}
多亏了st表,阿黄才能打败所有恶魔
…
”阿黄这么厉害,这一阵一定过的很轻松吧
阿黄望着村口几座大山,叹了口气
“是啊,很轻松…吧”(妈的现在一看两年前的子弹正中眉心)
…