本文章写给新手,大佬请出门右转
序言
一些问题最后常常转化为需要快速对序列进行一些操作与查询,这就需要使用数据结构来维护信息。本文介绍了几种常见且实用的数据结构与问题。
先介绍几个概念:
我们用 O ( O( O(预处理复杂度 ) − O ( ) - O( )−O(修改复杂度 ) − O ( ) - O( )−O(查询复杂度 ) ) )来描述一个数据结构的复杂度。如一个算法预处理复杂度 O ( n l o g n ) O(nlogn) O(nlogn),每次修改复杂度 O ( l o g n ) , O(logn), O(logn),每次查询复杂度 O ( 1 ) O(1) O(1),那么记它的复杂度为 O ( n l o g n ) − O ( l o g n ) − O ( 1 ) O(nlogn) - O(logn) - O(1) O(nlogn)−O(logn)−O(1)。
如果该数据结构不支持修改,或修改复杂度与暴力相当,则记它的修改复杂度为 O ( N U L L ) O(NULL) O(NULL)
静态问题:没有修改,只有查询。
离线问题:先处理所有修改,再查询。
在线/动态问题:修改查询随意进行。
加法原理:如果区间的一个量满足加法原理,说明它的答案可以从两个更小的区间的答案拼起来得到。满足加法原理的量比如区间和、区间乘积、区间异或和、区间最值等。不满足加法原理的量比如区间众数、区间最长不下降子序列长度等。
减法原理:如果区间的一个量满足减法原理,说明它的答案可以从一个更大的区间和另一个区间的差得到。满足减法原理的量比如区间和、区间乘积(如果取模则需要逆元)、区间异或和等。不满足减法原理的量比如区间最值、区间众数等。一般来说,减法原理比加法原理更为苛刻,也就是说一个量满足加法原理是它满足减法原理的必要条件,一个量满足减法原理是它满足加法原理的充分条件。
上面看不懂也没关系反正可以具体问题具体理解
接下来的每种问题基本上都有多种做法,最常用或最优秀、接下来要介绍的做法加粗表示。
切入正题
从最简单的问题开始:
静态区间和
问题描述:每次询问 a l + a l + 1 + . . . + a r a_l + a_{l + 1} + ... + a_r al+al+1+...+ar的和。
问题特性:静态、满足减法原理
同类问题:静态区间积、静态区间异或和
做法:线段树 树状数组 前缀和
没错你没有看错线段树是万能的
前缀和是一种非常有用的小数据结构,很方便且很容易扩展到高维。它适用于静态且满足减法原理的这一类问题。
用 s i s_i si表示 a 1 + a 2 + . . . + a i a_1 + a_2 + ... + a_i a1+a2+...+ai的和
预处理时 s i = s i − 1 + a i s_i = s_{i - 1} + a_i si=si−1+ai
查询只需返回 s r − s l − 1 s_r - s_{l - 1} sr−sl−1即可。
比较显然就不赘述了。
复杂度: O ( n ) − O ( N U L L ) − O ( 1 ) O(n) - O(NULL) - O(1) O(n)−O(NULL)−O(1)
离线区间加
问题描述:有一些操作,每次将 a l , a l + 1 , . . . , a r a_l, a_{l + 1}, ..., a_r al,al+1,...,ar加 k k k;输出所有操作之后的新数组的每个数 a i a_i ai。
问题特性:离线、满足减法原理
同类问题:离线区间积、离线区间异或和
做法:线段树 树状数组 差分
差分是一种快速的算法,适用于离线且满足减法原理的这一类问题。
构建一个新数组 b b b,使得 b 1 + b 2 + . . . + b i = a i b_1 + b_2 + ... + b_i = a_i b1+b2+...+bi=ai。
易得 b i = a i − a i − 1 b_i = a_i - a_{i - 1} bi=ai−ai−1。
这里可以看出 a a a是 b b b的前缀和数组。我们把 b b b叫做 a a a的差分数组。
对于唯一的 a a a或 b b b,它们对应的 b b b或 a a a也是惟一的,也就是 b b b是 a a a的双射
我们考虑对 a a a的一次操作将 a l , a l + 1 , . . . , a r a_l, a_{l + 1}, ..., a_r al,a