目录见右上
站内文章欢迎各位转载,但是请注明出处(毕竟码字很辛苦哈)。

主题

几类分块

(以下均为学习了栋栋的根号算法之后的总结)

  • 一类分块

这类分块还是比较简单的。

一般我们先确定一个阈值Threshold,一般是sqrt(n)。也就是说,我们每一个块的大小是sqrt(n)的。并且块的个数是sqrt(n)的。

先来看一下分块是怎么处理区间问题的。


例1-1:

6+9

题目描述

给出一个长度为N的区间。

现在给出M个操作:

Q l r:查询[l..r]区间内有多少个数由“6”或者“9”或者“6与9”组成。

A l r w:将[l..r]区间中所有的数加上w。

输入输出格式输入格式: 

第一行是2个正整数,N与M。

第二行是N个正整数,描述原始的序列。

接下来M行,每行第一个字母为“Q”或者“A”,之后有2个或者3个正整数,表示参数。

输出格式: 

对于所有的Q操作,输出答案。

输入输出样例

输入样例#1:

5 3

6 9 69 96 669

Q 1 5

A 1 5 1

Q 1 5

输出样例#1:

5 0

 说明 

N,M<=10^5

1<=l<=r<=N

0<=w<=10^4

保证初始序列以及操作后的序列所有的数A[i]均满足:0<=A[i]<=10^4

(注:原题为“4”、“7”、“4与7”。由于不会构造数据,改为了“6”、“9”、“6与9”)


个人解法:

(引自广东五校联考的第六场)

怎么处理?发现A[i]<=10^4。

于是在10^4内的仅有6与9组成的数只有30个。于是我们可以枚举这30个数,将问题转换为“区间加一个数w”与“区间查询有多少个w”。

树套树?第一棵树维护区间,第二棵树维护桶?绞尽脑汁想压位?

让我们分块。设阈值Threshold=sqrt(n),一共有sqrt(n)个块。每个块维护一个桶与一个标记。

那么空间复杂度是O(A[i]*sqrt(n))=O(10^4*300),是可以支持的。

那么怎么处理?


对于修改:

如果跨了整个块,那么这个块的Tag+=w。剩余部分,我们暴力修改,之后重构这个块。

跨块的修改是O(sqrt(n)),暴力修改与重构是O(sqrt(n))。

对于询问:

如果跨了整个块,那么就是查桶内w-Tag的位置有多少个数。剩余部分,我们先把l与r所在块的Tag下传(修改A数组),之后再重构一下l与r所在的块。接下来暴力扫描就可以了。

跨块是查询是O(sqrt(n)),重构与暴力扫描是O(sqrt(n))。


由此总的复杂度是O(msqrt(n))。


代码如下:

https://code.csdn.net/snippets/2027146


块维护桶是一个很好的方法,空间比树套树小得多,时间复杂度为差不到哪里去。

从上一个例子中,我们可以看见,我们在查询或者修改中,需要知道一整个块的信息,以及剩余部分的信息。剩余部分的答案往往需要重构块或者暴力扫描,时间复杂度取决于块的大小Threshold,而一整个块的信息我们要枚举块,时间复杂度取决于块的个数。

由此分块很重要的就是平衡块的大小与块的个数。这个思想在第二类分块中更为有用。

块维护标记是一个常用的技巧。很多区间+w的操作,都可以用标记实现O(sqrt(n))(一般也是这样)


例题1-2:

区间k小值

题目描述

给出一个长度为N的序列,与M个操作。

要求支持操作:

1 l r w:表示给[l..r]的区间加上w。

2 l r k:表示查询[l..r]的区间第k小的数。

输入输出格式输入格式: 

第一行为两个正整数n与m。

接下来一行,共n个数,描述原始序列。

接下来m行,每行4个数,表示操作。

输出格式: 

共m行,对于每一个2操作,输出答案。

输入输出样例 

输入样例#1:

8 8

434 208 378 245 421 43 98 35

1 5 8 62

2 1 2 2

2 2 8 3

2 2 5 2

1 1 8 472

2 1 7 2

1 1 5 40

2 3 4 2

输出样例#1:

434

160 

245 

632 

890 

说明

N,M<=10^4

(原题数据范围M,N<=10^5,时限为7000ms)


个人解法:

区间k小?主席树?

然而带了区间+w的操作……主席树?我不会(不过看到BZOJ上说还是可以做的?)。

我们来说分块吧。

分块。每个块内维护加法标记是显然的。应该还要维护一个升序的数组。


对于修改:

有了上一个题的经验就很简单了。跨块的,标记+=w。剩余的的,暴力修改,重新排序。(这里带一个log(sqrt(n))的排序,不过似乎log(sqrt(n))没什么大的影响)

时间复杂度O(sqrt(n)*log(sqrt(n)))

对于查询:

记得一个二分最平均数的题来着?有这么一种方法,把查询第k小转换为二分判断问题。那么在这里,我们同样可以二分查找,之后查找小于或等于它的有多少个。

对于整个块的信息,我们可以二分查找有多少个小于或者等于w的数。对于不是整个块的,重构一下块与A数组,之后暴力扫描即可。

时间复杂度O(sqrt(n)*log(maxn))

细节问题:由于我们不知道这一段区间的数具体是哪一些,所以我们只能盲查。这样可能会有一个问题,就是我们最终查到的那个数可能不在这个区间内。由此如果找到最小一个数w,使得小于或者等于w的数有k个。这个w就是我们要找的第k小的数。


所以总的时间复杂度为O(m*sqrt(n)*log(maxn))


代码如下:

https://code.csdn.net/snippets/2036883


同样,块维护升序数组也是一个常用的手段。



接下来我们看到第二种分块。

  • 块状链表

写过一遍不是很想写了

块状链表


接下来是第三类分块。

  • 在线莫队

虽然这是和在线莫队是有不同的,但是大体思路一致。暂且这么理解吧。

有一份在线莫队的报告,总结在莫队中。可以先看一下里面关于在线莫队求和的方法。

莫队

我们还是看一下“第三类分块”。

通常这一类分块是不支持修改的。一般是处理出关于块的答案Ans[i][j],表示第i个块到第j个块的答案。同时处理出元素关于块的前缀信息Sum[i][j],表示第i个元素在[1..j]的块中的信息。

例题3-2:

原题:https://www.lydsy.com/JudgeOnline/problem.php?id=4241

4241: 历史研究

Description

IOI国历史研究的第一人——JOI教授,最近获得了一份被认为是古代IOI国的住民写下的日记。JOI教授为了通过这份日记来研究古代IOI国的生活,开始着手调查日记中记载的事件。

日记中记录了连续N天发生的时间,大约每天发生一件。

事件有种类之分。第i天(1<=i<=N)发生的事件的种类用一个整数Xi表示,Xi越大,事件的规模就越大。

JOI教授决定用如下的方法分析这些日记:

1.选择日记中连续的一些天作为分析的时间段

2.事件种类t的重要度为t*(这段时间内重要度为t的事件数)

3.计算出所有事件种类的重要度,输出其中的最大值

现在你被要求制作一个帮助教授分析的程序,每次给出分析的区间,你需要输出重要度的最大值。

Input

第一行两个空格分隔的整数N和Q,表示日记一共记录了N天,询问有Q次。

接下来一行N个空格分隔的整数X1...XN,Xi表示第i天发生的事件的种类

接下来Q行,第i行(1<=i<=Q)有两个空格分隔整数Ai和Bi,表示第i次询问的区间为[Ai,Bi]。

Output

输出Q行,第i行(1<=i<=Q)一个整数,表示第i次询问的最大重要度

Sample Input

5 5

9 8 7 8 9

1 2

3 4

4 4

1 4

2 4

Sample Output

9

8

8

16

16

HINT

1<=N<=10^5

1<=Q<=10^5

1<=Xi<=10^9 (1<=i<=N)


个人解法:

我们要维护的是最大的“t*(这段区间内重要度为t的事件个数)”。就算是用分块,这个东西很不好维护。

但是没有修改。

于是我们可以考虑预处理。

我们分块之后,Sum[i][j]表示第i小的t,在前j个块中,出现的次数。这个可以O(nsqrt(n))处理出来。

接下来我们处理Ans[i][j],Ans[i][j]直接维护我们需要的信息就好了。怎么维护呢?我们考虑到Ans[i][j]是可以由Ans[i][i]与Ans[i+1][j]维护得来。由此我们可以参考区间DP的思想。

Ans[i][j]=max{Ans[i][i],Ans[i+1][j],T[A[k]]*Times[A[k]]},其中A[k]是第i个块中的元素,Times[A[k]]表示A[k]在[i..j]这段块中出现的次数。

这样我们就把块与块之间的答案处理出来了。


对于询问:

我们可以先找到Ans[pl+1][pr-1](pl为l所在的块,pr为r所在的块),先作为答案。

之后枚举剩余部分的元素,用一个桶维护剩余部分的元素出现的次数,并且通过Sum得到它们在整个询问的区间中出现的次数,用t*Times来更新答案。


时间复杂度O(msqrt(n))。


细节问题:当然数字比较大,需要离散。


代码如下:

https://code.csdn.net/snippets/2043791


看上去不可做的题就这样被预处理干掉了。

之所以要预处理Ans[i][j],是因为我们如果只有单个块的信息,我们不能迅速得出[i..j]这一段块所包含的区间的信息。

这个时候我们就通常可考虑通过预处理来实现。


如果强行需要修改怎么办?

发现,如果我们需要修改,那么我们可能需要重新构造Ans数组,花费时间代价极高。考虑到之前提及的在线莫队。

在线莫队是没有处理出Ans[i][j]的,这样使得它在查询的时候需要先扫描[pl+1..pr-1]的块来统计答案。但是对于修改,由于它没有和很多元素关联,是可以实现O(sqrt(n))的维护。

当然这类修改亦可以通过构造其他信息来维护。类似某锟上次出的一道位运算的题,不过构造这种玄学的东西……


笔者水平有限,故笔记至此结束。

评论
©主题 —— Powered by LOFTER