数据结构

分块&莫队

(;)

对询问分块

(;)
询问数较少的情况。
我们记录下当前的(T)次操作,在查询的时候,只需看(O(1))地检查每个操作对查询区间的影响
这样做到查询是(O(T))
那么每(T)次操作后,需要重新计算当前全局的局面(如计算前缀和)
这样总复杂度是(O(mT+n frac{m}{T}))
(T=sqrt n)时,复杂度最优为(O(msqrt n))

P3863

(;)
考虑扫描线,扫序列的维度,数据结构维护时间轴
那么一次修改可以看作平行于x轴的一条线,扫的过程中,如果发现有一条线的左端点,要支持后缀修改(会影响后面的时间),代表序列的这个位置相比上个位置+x
那么我就可以快速的去维护一个位置在所有时间的值,转化为一个区间有多少个数(<x),直接分块来做
区间加,区间rank问题

区间出现次数平方和

(;)
注意,莫队的复杂度是(nsqrt m),而不是(msqrt n)
所以询问越多,它跑的越快

AHOI2013作业

(;)
四个限制,莫队维护了两维,直接树状数组会多个log
但发现修改比查询多的多,于是考虑根号复杂度平衡,支持一个修改(O(1)),查询(sqrt n),直接分块就可以了
发现莫队其实就是个二维的扫描线
(;)

历史研究

(;)
发现可能的答案只有(n)
先处理出所有答案,离散化后放到一个序列上,并对其进行分块
在跑莫队的时候,把其对应的答案放到我们处理出的那个序列上
这个需要支持(O(1))插入,然后查询时从大到小,若有元素再在这个块中找,显然是(sqrt n)

(;)
出现了两个区间,且是出现次数的乘积。
发现维度太多,想象能不能消去一些。
发现这东西是有差分性的
(f(l_1,r_1,l_2,r_2)=f(1,r_1,l_2,r_2)-f(1,l_1,l_2,r_2)=f(1,r_1,1,r_2)-f(1,r_1,1,l_2)-f(1,l_1,1,r_2)+f(1,l_1,1,l_2))
中间应该是一些-1这里我没写
然后就只有两维了,用莫队直接维护,维护两个序列,新增一个点时,看它在另一个序列的出现次数就行了

CF1476G

(;)
发现出现次数最多有(sqrt n)种。在莫队的时候处理出现次数的序列,然后双指针扫就可以

经典题目

(;)
(n)个点(m)条边的无向图,支持单点修改,求和一个点相邻的所有点的点权和
(m)是1e5级别,所以度数的总和不超过(2m),考虑根号分治。
度数(<sqrt n)的暴力处理,对于度数(>sqrt n),数量很少,在修改的时候,直接处理这个点的修改对度数大于(>sqrt n)的点的影响

(;)
插入(x),求序列中(mod;y)最小值
(y<sqrt n),对于每个数,预处理出其mod y的值,在询问时,直接取个全局min
(y>sqrt n),把序列从小到大排序后,分块。对于(y,2y,...,ty)分别求大于它的最小的数。直接在块上跳就行

平衡树 & 线段树

(;)
两种:维护序列权值,查询k小值;插入删除,翻转一段区间

Splay

(;)
rotate:如果三点共线,就先旋父亲,否则先选自己。最后再旋转自己
merge:把靠左的区间的最右的点旋到根节点,然后直接把右区间接在它的右儿子上
split:把第k小的旋到根节点,然后直接拆

P4198

(;)
每个点算个斜率
维护一下区间斜率最大值,有点像cdq分治,考虑左边对右边的影响,这样合并的时候最多递归其中一个儿子,因为另一个儿子已经被另一个儿子算过了,pushup复杂度是log的

P4036

(;)

有单点插入,一眼平衡树
查询时,直接二分。然后把长度为mid都旋成一颗子树中,那么只需要看两颗子树的哈希值是否相同
支持平衡树单点插入,维护区间哈希
一次查询操作是O(log^2 n)

(;)
和那到cf div1 B 有点像
先去维护区间min,max
直接去维护区间差分数组的gcd,至少需要是k的倍数
判重:维护每个点的pre。代表上一个a_j=a_i的地方
维护一个pre的最大值

P3215

(;)
括号序列匹配
一定是先把能匹配则匹配,剩下的就是"))))(((((",这样的东西
然后合并就是看左括号,右括号哪一个多。
invert就再维护一个序列,rev暂时也是,但lxl说可以直接打标记

李超线段树

(;)
在每个点上打个永久化的标记。
来了一个新的线段后,考虑把那个较高的部分较短的线段下放,这样一定只会下方到其中一个儿子中。
这样每个线段会被下放log次
例题:P4097,P4069

P5608

(;)
考虑标记的合并,和值和值得合并都很好做。
但是标记对值得影响不好做。
考虑维护极长的"x"段,这样不同长度的段最多有根号种
如果维护一个光速幂,一个节点计算的复杂度为(O(sqrt {size}))
虽然为log个节点,但为同一层数最多有2个节点,计算下来还是根号
至于合并的时候,直接把两边的多项式归并

线段树/平衡树要注意的问题

(;)
标记对标记可合并
标记对值的影响快速计算
值对值(儿子的值可以快速合并到父亲)

区间赋矩阵,区间乘法

(;)
线段树直接按2的整数幂建
那么线段树上每个节点的大小也是(2^x)的形式
所以对于(m)种操作的每一个矩阵,都只用预处理(log(n))种幂
一个log的复杂度

CF453E

(;)
我们发现,每一次询问,都会让一个段变为0
不妨维护一段一段的区间,每个区间上记录它是什么时候变成0的
那么一次询问,一定会让区间的段数变小(也有可能+1,但并无大碍)
所以维护一颗平衡树
那一段一段的去解决,总均摊是O(n)的段数
而对于每一段说, 对于已满和未满的分别考虑
已满的就是(t_i<t)(m_i)的和
把这个问题看作一个二维平面的问题
把询问离线下来,用扫描线支持单点修改,区间查

T-shirt

(;)
对于人建一颗平衡树,对于一件t恤,相当于要把整颗平衡树中(geq c)的数(-c)
但如果直接把那课子树打上标记,可能导致不满足二叉搜索树性质。
所以对于([c,2c])的部分暴力删除+插入,其余部分打标记。
这样暴力部分最多会有log次,且其余也能满足性质,然后去单点+1(树状数组)
复杂度:两个log

P5610

(;)
对于这种除(x)的操作,先要去关心值域。
发现值域很小,就把每种(x)它的倍数处理出来
然后对于/x,在x这颗平衡树里找([l,r])这段区间,如果发现已经不满足条件了,就直接把它从这棵树中删去
这样总点数是(n; log; n),均摊后总复杂度是(log^2 n)

CF438D

(;)
区间取模,区间和
容易发现,一个数对(p)取模之后,值至少会减半。
所以我们维护区间最大值,如果发现(geq p),那么直接暴力去递归。
这样每个点最多取模(log)次,且在线段树上递归一个点的时候复杂度也是(log)
总复杂度:两个log

HDU6315

(;)
区间(a_i)+1
询问(sum_{i=l}^r frac{a_i}{b_i}),注意这里是下取整
和上道题类似,同样是抓住所有点的答案的和至多会变(n;log;n)次的性质(调和级数)
每个点初值为(-b_i),如果发现区间max(<0),那么不需要修改,只用打个标记
否则我们就要暴力递归,找到0的位置,改成-1,然后把它的答案+1
也是两个log
和上道题基本上一模一样的思路

P7447

(;)
([l,r])(>x)的元素(-x)
询问和,min,max
(;)
一种值域分块方法
(;)
分为log个块,分别为([0,1),[1,2),[2,4),[4,8)....)
那么就可以对于每个块开个线段树/平衡树维护
也是找到([l,r])在平衡树中的位置
块的值域(<x):不用管
块的值域(in[x,2x)),修改后必定会掉到下面的块中,就要删掉,并插在它掉到的那棵平衡树上
块的值域(geq 2x),维护最小值,看是否要往下掉
这样最多掉落(log)
复杂度还是两个log

P5066

(;)
维护所有极长的0,1段
考虑或操作:让区间内所有1段长度+1,0段长度-1
与操作是相反的
所以不妨维护一个区间内0,1段个数,长度的最小值
如果发现最小值变为0了,肯定往里面递归进行合并,容易发现合并的次数是(O(n))的,所以暴力递归是正确的

CF1446D2

找最长的子段,使得众数的个数至少为2
首先有一个性质,这些众数,其中的一个一定是原序列的众数
因为从序列两端开始删,第一次有两个数出现次数相等且最大,那么肯定其中一个是原序列的众数。
假设这个众数是(a),那么我们对于每个值(x),枚举其所有位置,并对其左/右未被标记的第一个(a)打上标记。
只需考虑那些被打上标记的位置,且只有(O(|x|))
再搞个前缀和,转化为1,-1,对于每个(sum_i=x),找出最小和最大的(i)即可

可持久化数据结构

CF464E

(;)
无向图,每条边长度都为2的整数幂
求s到t的最短路,取模。
把每个点的dis可以表示出一个01串,用线段树来维护。
那么比较两个01串的大小直接在线段树上二分,一个log
在进行(u->v)的转移时,会出现进位的情况。
于是可能要进行一些区间赋0,单点修改为1的操作。
在线段树上找到这些节点,新建到新的树上,并把它们影响的父亲也要新建节点
你会发现这就相当于把一段区间拆分成了log个节点,每个节点到根的路径上的点的并,这样也一定是log个节点(并不是(log^2)个)
但节点可能不是叶子节点,就不能再往子树内递归去新建节点,这样空间就炸了。
所以只在上面打上标记,等以后往下递归的时候,再新建儿子节点并下放标记。
加上dijkstra,复杂度是(O(nlog^2 n))

一道codechef的题

(;)
输出两个树上路径按点权从小到大排序后不同的位置
发现总不同位置只有1e6
这里是判断两条路径是否相同,如果在主席树上只维护了某值域中出现了多少个数,不大好做。
所以再维护一个hash值。
找的时候,还是运用差分的思想。
这样我们实际上是对于相同的值域,看它们的位置不同的数量
在权值线段树找,如果发现hash值不同就暴力递归,因为总共只有1e6不一样,所以是对的

扫描线

(;)

HH的项链

(;)

对于区间不同颜色数问题,常见套路:维护(pre_i),代表上一个同一颜色的位置
问题转化为:查询([l,r])值小于(l)的个数,这个相当于一个3-side的矩形,可差分(很多题往往是离线+差分可以干掉一个关键字)
扫描线从左往右扫,支持单点修改,区间查询

bzoj3489

(;)
查询区间中有多少数只出现一次
直接把((x,y))看成区间([x,y]),然后对于一个数(x),找到它的pre和nxt,显然只有在这个区间内的区间在能被它贡献到
发现又是一个矩形+1,查询单点问题,扫描线解决

bzoj4212

(;)
多个字符串。多次查询,给出字符串s,t:有多少个字符串满足s是其前缀,t是其后缀
把所有字符串扔到一颗trie上(正着建+反着建)。
查询时,在前缀trie上找到那棵子树,再在后缀trie上也找到那子树。
那合法的字符串肯定同时存在于两颗子树中
对于子树,又是套路,通过dfs序,转成序列上的问题。
假如一个字符串s,在前缀trie的第x个位置,在后缀trie的第y个位置,那么其可以抽象为平面上的((x,y))
所以一次询问相当于询问一个矩阵和。
修改就是单点加

区间的子区间

(;)
把l看作一维,r看作一维,那么所有子区间是一个三角形(y=x以上的),可以看作2-side矩形

CF997E

(;)
多组询问:求一个区间的多少子区间包含连续的一段值域
转化为:(r-l-(max-min)=0)
把每个区间都看作平面上的一个点,先构造出这个矩阵。
如何构造:一个位置可以分为四部分:r,l,max,min,分开考虑
r,l:比较简单,一行一列的加(其实相当于(1 imes n, 1 imes m)的矩形
max,min:不妨去分治,找到max/min,然后将序列分成两部分,对应平面上的一个矩形
按照扫描线的套路,先把这些矩阵加的操作离线下来。
询问相当于求一个矩阵的和。
在纵轴上维护一颗线段树,设(b_i)为当前第i个位置历史(a_i)的总和。
扫描线每往右滑动一次,相当于会改动所有的(a_i)(当前列的和)
在root上打个tag,即 :(tree_i+=sum_{a_i} imes c)相当于把整个序列的(b_i)都加上了(a_i),还要维护一个历史加的和(这个非常重要),和上次被标记的时间
那么就可以合并标记了

P5070

(;)
扫右端点,数据结构维护左端点的答案。
(a_r)的值是(x),那么我们现在只用考虑前面在([x-9,x+9])中的数,对剩余的数显然不会产生贡献。
还是预处理(pre_i),那么我们首先找到这20个数在([1,r])的最晚出现位置,分成20段
对于每一段,都相当于,现在已经在长度为20的这个区间里有一些值域连续段了, 然后我们新增了(x),会产生的贡献自然就是合并两边的段产生的长段(或是一个段的开头/末尾)
所以对于每一个长度(in[1,10]),都要要支持一个区间修改,单点查询,显然维护10个树状数组就行了

LOJ3489

(;)
按照套路,序列和时间分别看做一维。正常来说,这个修改应该是后缀的修改,但这里我们把它改成单点,那么查询相应的改成前缀
对于询问的前缀,假如说我们可以找到最晚的位置满足队列是空的,那么之后,相当于加入和弹出操作,就与之间的顺序无关了。
只需要找到插入中第(t+k)个元素(假设共删除了(t)个元素),这个玩意直接在线段树上二分就可以了。
而最后一个为空的位置,一定是最大后缀和的位置。
首先往后不可能右空的地方(不然往后移会更优)
且这个位置一定是空的,不然可以找到从这里往前的一段区间为正,那么从那里开始累加,到这里就会有元素

LOJ6276

(;)
查询树上有多少条路径满足颜色数不同(每种颜色数量$leq(20) 看着像点分治,实际上是诈骗题 考虑同种颜色点)x,y(,)x(子树的点组合上)y$子树中的点的路径都是不可选的。
按套路求出dfs序,变成了矩形。那么只需要求出所有矩形的并就可以了,然后再取个补集就可以了。
矩形最多有2e6个,扫描线解决

KD树

求区间逆序对

(;)
扫描线+kd树
扫描线扫(r),显然把所有(a_i>a_r)的点的值+1
查询时,就会求所以(xgeq l)的点求和,就可以kd树了

NOI2019弹跳

(;)
仿照dijkstra的思路,每次找到一个dis最小的点,然后松弛其连到的点
现在一个点连到的点是一个矩形,太套路了,相当于我们要进行矩形取min操作。
那么前提是我们找到一个dis最小的点,也就是求全局min。
结合起来就是kd树来搞

二进制kd树

(;)
反正就是分为了log个kd树,每个kd树的点数都为(2^c)的规模
在修改和查询的时候,就在这log个kd树上分别去搞,这样是(sqrt n + sqrt{frac{n}{2}}+sqrt{frac{n}{4}}+...+1)也是根号级别的
那么如果插入了一个点,若发生进位,相当于把若干个kd树合并了起来,变成了一个更大的kd树,那么我们直接暴力去合并。
这样每个如果被合并了一次,一定代表其所在kd树规模至少乘2,所以一个总合并次数为(n;log;n)
而对于建立kd树,如果有n个点,其复杂度是(O(nlog n))的,所以总插入复杂度是(O(nlog^2 n))

树套树

(;)

P3380

(;)
树状数组套值域线段树。
主席树是两个点进行整体二分
现在是log个节点进行整体二分。然后在二分的时候一起往下走
复杂度是两个log

P4054

(;)
虽然有3个维度(矩阵+颜色)
但发现颜色具有特殊性,也就是每次询问都是求(a_{i,j}=c)的个数这样的形式。
对于这样的维度往往可以是省去的。
发现值域也很小,这里开100个二维树状数组

动态逆序对

(;)
求矩形点权和,和单点修改点权

P3242

(;)
还是按照套路,先搞dfs序。
那么问题变成了:单点修改,矩阵里求第(k)
怎么维护呢?
先把所有矩阵和修改啥的都离线下来,扫描线从左往右扫。
如果把矩阵改成序列的话,直接就是一个树状数组套值域线段树的事。
只不过增高了一维。
按照在整体二分的思想,可以把这个树套树可持久化下来。
然后对于询问矩阵来说,对于两个版本的树套树差分一下,就可以快速得到这个矩阵中值在([l,r])的数有多少个了
外层可持久化树套树的整体二分一个log,在内层的树套树中也是一个log。
(O(nlog^2 n))

P3242(变式)

(;)
反过来想,就是一个矩阵都插入一个数,然后询问单点的第(k)
因为我只查询单点,问题反而变得更简单一些,就不用可持久化了
支持区间插入,区间删除,单点查第k大。
维护一颗线段树套值域线段树,区间插入时,就在线段树上对应的节点所表示的权值线段树的相应值域+1
因为我们标记永久化了,只用看一下根到它的路径上的所有权值树,也是搞一个整体二分

bzoj3489

(;)
问题转化为矩阵取max,单点求值。
首先还是离线下来。
因为我们只关心最大值,所以原先的权值线段树变成了堆
也就是我们维护了一个树套堆,但这里还要支持删除,堆删除的话就要用到对顶堆
对顶堆:在原先大根堆A的基础上又维护了一个大根堆B。如果删除的话,先把这个点扔到B堆中。
在任何时候,如果发现两个堆堆顶相等,就把俩都pop掉。
容易发现:B是A的子集,且稍微有些双指针的思想

P5445

(;)
把全是1这样的连续段先用set维护一下。
如果点亮灯,相当于两个段的点联通了,就把一个矩阵上的点加上(inf-t)
等到之后如果不联通了,就把这个矩阵中的所有数加上(t'-inf)
这样你会发现刚好加上了(t' -t)
直接一个矩阵加,单点查历史和的东西。(其实也不用维护历史信息,因为那个操作已经自动累加了历史联通的时间和)
一个纯纯的树套树问题

比赛

(;)

A

(;)

直接启发式合并(不要看到子树就被固定思维转化到序列上)

B

(;)
为了防止算重,记录一下每种数出现的最左和最右的位置,然后就是带修改的偏序问题
直接树套树维护(我被卡常了

C

(;)

按询问的角度来看不好做
我们考虑启发式合并,假设现在的根是(x),下面挂着若干颗子树。
对每个点维护一个set,在枚举轻儿子子树中所有点时,看一下set中其前驱和后继。
然后把包含它们的所有区间都要加上x这个LCA(相当于一个矩形)
但为什么其他点不用管,因为前驱后继所表示的矩形一定是最大的,会把其它矩形给覆盖掉。
然后就是搞一下矩形求并
但发现这里是2-side矩形,所以可以把这些(k)个矩形再改成另一形式的(k)个不相交的,就是区间加了,扫描线解决

LCT

基本操作

(;)
access:不断把它旋到splay的根,从(x)接到(y),还需把(y)旋到树根,然后把(x)替换(y)原先的右子树
makeroot:先access,然后把其到根节点的重链翻转
cut:先把(x)变成根,在对y进行access,然后在把(y)旋到splay的根,然后把(y)和其左子树分离
link:先把(x)变成根,再把y变成其父亲,相当于一条虚边
isroot:判断其父亲的左右儿子只要有一个是x,那么x就必定不是splay的根
select:先让x变成根,在access一下y,然后splay维护的就是这条路径了
注意access的时候要先把其到根路径上的点的标记先下放

P3203

(;)
按弹射关系连边,构成一棵树,支持link和cut
求其到根的距离就可以了(大概access一下看splay的size)

P3703

(;)
这里染成一种从没染过的颜色是很重要的一条性质
那么可以把这个路径染色看成抽象成access。
而与序列颜色段数类比,发现这样access均摊的次数就是(O(n+m))
然后找x->y的颜色段数。
因为这里涂色很特殊,是点到根的路径。
所以一种颜色不可能被分成了两段。
找出它们的lca,差分一下,注意这里要特判一下距离lca最近的一段颜色,讨论一些情况
维护一下每个点到根要经过重链的个数,那么子树求最大就转化为dfs序
【根到路径】联想到access

P4172

(;)
让路径的最大值最小,容易发现只需要找到最小生成树
而删边维护最小生成树不是很好做。
还是按照套路,考虑加边。
如果原先不联通:直接加
否则,把x->y的路径上最大值的边给去掉。LCT就很好维护了
【动态维护最小生成树】

P2387

(;)
有两个关键字,求1->n两个关键字max的和最小是多少
发现这题没有什么查询操作
那么考虑从小到大枚举(A)的max,相应的可以加入一些边【link】
最小化b的最大值,也是求在b的图上求最小生成树,和上题一模一样

loj6038

(;)
显然维护一下直径就好了,而合并两棵树,新的直径也一定是原来的4个端点的两个之一
【动态维护直径+距离】

P4230

(;)
双指针,支持link,cut,就可以找出每个(l)为左端点时,最小的(r),满足其是合法的序列。
单独考虑以(l)为左端点的序列,相当于是一个加上等差数列,线段树维护

CF1017G

(;)
好妙的一道题
一个点只会被祖先染黑,加入把一个点染黑,就把这个点权值+1。
那么一个点被染黑当且仅当到根路径上最大后缀和>0
考虑染白的操作,求出最大后缀和t,然后把点权减去t,发现这样刚好满足条件(这玩意怎么才能想到啊)

CF1109F

(;)
更妙了
有环了之后肯定就合法了,还是双指针找到第一个没有环的地方。
考虑这段区间有多少个点(i),满足([i,r])是一棵树(判断不是森林)
又是常见套路:点数-边数=1的才是一棵树
考虑维护这个东西
扫描线往右扫的时候,直接区间+1,考虑与它相邻的四个位置,在区间上找到这四个位置,分为四段,相应的进行区间-1,-2,-3,-4操作
最后找有多少个位置满足其值=1,显然也可以用维护区间最小值和个数的方法来做

原文地址:https://www.cnblogs.com/czyty114/p/15079640.html