处理多维关系汇总
什么是多维关系
多维关系基本上指偏序关系,即一个元素包含多个属性\((a,b,..)\),每次需要快速查找满足某一关系的所有元素,再进行整体处理(权值和,第\(k\)大...),如最基本的逆序对,每一个元素包含了位置和权值\((pos,val)\),对于每一个\(i\),需要查询的是满足\(pos_j<pos_i\land a_j>a_i\)的\(j\)的个数。
在处理这类问题的时候,首先需要将题目包含的关系找到,再运用各种方法,不断消除偏序关系,最终使满足条件的元素在一个可以整体处理的数据结构中,就可以很好的解决了。
下面就将介绍一些消除偏序关系的方法。
排序
没错,最简单的方法就是排序。
设偏序关系是\(a_i<a_j\)(下同),那我们就按\(a_i\)降序排序,这样当处理\(i\)的时候,所有\(a_j>a_i\)的\(j\)就已经被处理好了,这时候我们就消去了\(a\)这一偏序关系。
当然,如果偏序关系是\(a_{j,1}<a_{i,2}<a_{j,3}\)(保证\(a_{i,1}\le a_{i,2}\le a_{i,3}\))的时候,我们按\(a_{i,2}\)排序后,可以满足\(a_{j,1}<a_{i,2}\),但\(a_{i,2}<a_{j,3}\)的怎么满足呢?其实只要在删除\(a_{j,3}<a_{i,2}\)的\(j\)就可以了。或许你已经听过这个思想,它还有另外一个名字——扫描线。
排序可以消除一维关系,剩下一维整体处理,但往往题目中的偏序关系不止两维,这时候就需要更高级的方法了。
KDT
KDT是用来处理在\(k\)维空间上,维护单点的二叉树/线段树结构。核心是将点进行不断划分,使整颗树的深度为\(O(\log n)\)级别,对一个矩形查询/修改一次的复杂度为\(O(n^{1-\frac{1}{k}})\)。
建树:选择方差最大的一个维度(注意方差计算时开double
),然后以该维度上的中位数划分当前区间,即将小于(等于)的放在左边,大于(等于)的放右边。然后递归处理。pushup
的时候维护包含所有点的最小矩形的边界。
查询/修改:正常修改,即当前矩形范围被询问矩形包含即操作,返回,完全不包含的情况就返回。然后由于某种奇妙的性质,整个过程的复杂度为\(O(n^{1-\frac{1}{k}})\)
两种不同的结构
-
线段树式:只有叶子节点才表示一个节点,空间4倍。
优点:处理方便,
pushup
时不需要考虑边界,pushdown
也少一些细节。 -
二叉树式:每一个节点就是一个节点,空间1倍。
优点:可以方便的支持动态加点(需要判断若某一个儿子的节点大小大于整颗子树大小的\(0.6\)倍时重构)。
特点:
-
空间\(O(n)\),但时间\(O(n\sqrt n)\),如果查询的点动态插入的话需要\(O(n\sqrt {n\log n})\)(插入\(\sqrt n\)的点重构整颗树),或\(O(n^{0.57})\)的子树重构。但是建好了的点的属性是不能改变的!
所以一般在已知要查询的点时可以使用KDT,需要修改点的属性的时候不能使用KDT。
-
由于其和线段树一模一样的结构,可以完全支持线段树的所有处理办法,如吉老师线段树。
所以一般在整体处理复杂但有结合律(这是所有可以拿标记维护的基础)的时候可以使用KDT。
-
复杂度和值域无关!
树套树
树套树,即在树型数据结构的每一个节点上再开一个树型数据结构。在第一个数据结构的处理中删掉第一个偏序关系,而第二个数据结构进行整体处理。
先说明一下内层与外层的最主要的区别:我们需要在多个独立的内层数据结构中得到结果并合并起来,所以不能处理第\(k\)大之类的问题。所以一定要选择好先消除哪个偏序关系,留下哪个做处理。
-
树状数组
-
内层
树状数组放入内层的时候无法动态开点,用
unordered_map
常数巨大,所以无法做内层。 -
外层
可以单点加、区间查,或区间加单点查,无法处理区间加,区间查。
只能求前缀min/max,不能求区间min/max
常数很小。
- 线段树(值域线段树)
-
内层
可以处理所有单点,区间问题,在处理区间问题时注意标记永久化,可以极大减小常数。
在有重点的情况下,处理比较困难。
有值域限制,而且空间为\(O(n\log^2 n)\)。
-
外层
和内层基本一致,处理此处标记无法下传,所以必须标记永久化。
-
-
平衡树
-
内层
处理无值域限制,空间为\(O(n\log n)\)
常数大
-
外层
好像要替罪羊树,没写过,唯一能多干的事就是支持动态插入。
-
树套树与KDT的比较
-
常数方面:最经典的动态二维数点,随机加入和查询\(10^5\)次,运行结果如下:
值域\(10^5\)内 值域\(10^9\)内 子树重构kdt \(1.45s\) \(1.45s\) 根号整树重构kdt \(1.65s\) \(1.65s\) 树状数组套线段树 \(0.75s\) 受值域限制 树状数组套平衡树 \(1.89s\) \(2.5s\) -
处理问题方面:
- kdt处理两维限制是一起消除的,所以无法处理某些只关系一维的问题,如第\(k\)大。
- 而树套树内层的每个点实际上有多种地方出现(即在外层的多个节点出现),那些需要前后关联的操作就无法实现,如区间赋值。
-
实际问题
-
二维平面,给定一些点,点带点权,每次操作查询矩形内最小权值的点,并删除。
首先查询是最小值,外层不能用树状数组,内层线段树处理困难,平衡树常数巨大,所以推荐使用kdt。
-
每次交换数列中两个数,查询整体逆序对数。
分析一下得:求数列在\((x,y)\)而且值在\((l,r)\)的数的个数,然后单点修改值
虽然是明显的二维数点,但涉及第二维值的修改,即需要维护删点和加点,难搞。不如直接树状数组套线段树。
-
给定每个元素是一个区间的序列,多次询问,给出 A,B,求出 a 中最长的区间(即这个序列中的一段),使得这个区间内每个区间都与$ [A,B]$有交集。
分析一下得:二维平面,有一些点,每次给一个矩形,使包含在矩形内的点权值+1,其余清零,求每个点的历史最值。
又是二维点的处理,但这里是要加、赋值、历史最值,赋值就根本无法处理,历史最值更没得搞。只能用kdt。
-
cdq分治
cdq分治是将动态(在线)的问题转化为静态(离线)的问题。
怎么理解这句话?
就好比这样一个问题:
有一个数组,数轴上有一些点,多次询问,每次询问给定一个区间,求区间所包含的点数。
如果提前告诉你所有的点,然后再去询问。这时候你知道,所有的点都可能会在区间中,就可以将点按坐标排序,在区间左端点后、右端点前的就在区间内。
但如果在询问之后又加了一些点,并且后面还有询问...这时候上面的办法就没用了,因为在询问后加入的点不可能前面的询问矩形内。这时候你按坐标排序,在区间左端点后、右端点前的点不一定在区间内。
上面这两种情况就分别对应了静态和动态(你也可以理解为动态情况多了时间轴的偏序关系,即只有时间前的才能贡献时间后的,这也是为什么我将cdq放在了这个专题里)
-
算法流程:
套用上面的问题,有一个时间先后的操作序列,\(\text{C}\)为加点,\(\text{Q}\)为查询。如\(\{C\;\;C\;\;Q\;\;C\;\;Q\;\;C\;\;Q\;\;C\}\)。
考虑从正中间划分\(\{C\;\;C\;\;Q\;\;C\;|\;Q\;\;C\;\;Q\;\;C\}\)
这时候你发现,在分割线左边的加点操作一定先与右边的查询操作\(\{C\;\;C\;\;.\;\;C\;|\;Q\;\;.\;\;Q\;\;.\}\)。
那么这就转化为了静态的问题,在给左边的\(\text{C}\)和右边的\(\text{Q}\)打上标记之后,就可以按照上面引入部分的方法解决了(处理的时候我们只处理带标记的加点和查询)。
处理完之后再去递归处理分割线左边的\(\{C\;\;C\;\;Q\;\;C\}\)和右边的\(\{Q\;\;C\;\;Q\;\;C\}\)。
当然,你可能会说排序之后分割线左右的已经不是原来那样了,没有关系,我们先递归再处理也是可以的。
-
不一样的cdq
上面所介绍的是最基本的cdq处理问题方法,其实还有很多情况是cdq可以处理的,下面是一些cdq的技巧(或许是)
-
偏序降维\(\rightarrow\)时间轴 (P3810【模板】三维偏序)
我们可以将一个静态的问题,将某一偏序关系排序之后,就变成了"只有前面才会贡献后面"的动态问题。
-
查询与修改的相互转化(P3810【模板】三维偏序)
这题中,我们没有修改,而查询满足条件的点对数。看似与上面我们介绍的修改和查询分开不同,但实际我们在处理的时候,就可以把分割线左边的点当做修改,右边的点当做查询。这样在查询到的点全是左边的点,满足题面的要求。
-
优化带依赖关系的转移 (P3769 [CH弱省胡策R2]TATT)
有这样的一个DP:\(dp_{i}=\max\{a_i\ge a_j\land b_i\ge b_j\land c_i\ge c_j\land d_i\ge d_j|\;dp_{j}\}+1\)。
同样,先排序消除一维偏序关系,转化为动态问题cdq分治后,发现这个dp转移好像又有所不同,其实我们可以将左边点当做修改(加入一个值为\(dp_i\)的点),右边的查询满足条件的最大值。
注意:由于一个点在转移时必须先知道其dp值,所以遍历分治树时必须按中序遍历,并且为了避免破坏原来的顺序,需要额外开一个数组去处理。
-
cdq嵌套(P3769 [CH弱省胡策R2]TATT)
在消除第一维偏序之后,其实还可以继续cdq消除第二维偏序,此时与三维偏序不同的是这里我们已经明确了修改和查询。
-
容斥计算答案(CF1045G AI robots)
当询问的偏序关系是\(a_i-k\le b_j\le a_i+k\)的时候,而我们内层为了让常数小而使用树状数组的时候,我们可以考虑将询问拆成两个,即加上\(\le a_i+k\)的答案,减去\(\le a_i-k-1\)的答案。
-
-
其他:
-
关于cdq与树套树和kdt的比较
cdq常数吊打另外两个!
cdq的处理方法类似树套树,在消除偏序关系的时候分割了修改,所以无法处理如区间赋值,区间加的操作。
cdq必须离线。
-
复杂度:
分治层数为\(O(\log n)\)层,看处理一层的复杂度是多少。一般是\(O(n\log^2n)\)
优化:如果处理一层只需要\(O(len)\),而需要排序,并且处理的问题非依赖转移类,可以使用归并排序优化到\(O(n\log n)\)
-
常见的偏序关系
排除最明显的\(a_i<a_j\)。
-
区间关系(P5445 [APIO2019]路灯)
\(\text{区间[l_1,r_1]包含区间[l_2,r_2]}\rightarrow l_1\le l_2\land r_2\le r_2\)。
\(\text{区间[l_1,r_1]与[l_2,r_2]相交}\rightarrow l_1\le r_2\lor l_2\le r_2\)。
-
树上路径关系(P3242 [HNOI2015] 接水果)
路径A包含路径B\(\rightarrow\)dfs序的关系(分\(lca_{x,y}=x\)和不等两种关系)
-
绝对值(P4169 [Violet]天使玩偶/SJY摆棋子)
若\(a_i\)和\(a_j\)的大小确定了,那么\(|a_i-a_j|\)的正负也确定了。
所以我们可以先处理有\(a_i\le a_j\)关系的,再处理有\(a_i> a_j\)关系的。
-
最小值最大值(CF1045G AI robots)
如\((x_i-x_j)\leq\min(r_i,r_j)\),我们可以以\(i\)去考虑所有\(r_i<r_j\)的\(j\),这样就可以将min去掉。
若遇到更多的应该会添加。
总结
做这类题的大概步骤就是:找到题目里的偏序关系\(\rightarrow\)选用合适的算法\(\rightarrow\)一定要对拍!