今天这个专题上的真是一脸mengbi啊。
讲的各种用法十分玄学,听懂的不是很多。
不过把点分治好好看了看,还切掉了一道点分+扩欧的题。
对我来说已经是很神的题了啊,居然没有调试很久就过掉了233.
=====================分割线=====================
树上问题啊树上问题啊,上午讲树剖+点分。
刚开始还好,然后就昏昏欲睡了。靠自己补啊啊。
今天讲的内容是关于树的一系列问题。
刚来到网络就出锅了,连内网都有点神神的,并不能屏幕广播了。
看得好累啊。
先讲了树链剖分,那就复习一下吧:
siz:子树大小
son:重儿子(siz最大的儿子)
fa:父亲
top:重链顶
dep:深度
pos:这个点dfs序在线段树上的位置
性质:每个重链的连通块都是一条直链,链之间不重和。
一个点往上最多经过log条重链,log条轻边。
用途:
求LCA:两个点在一条重链上时较浅的点就是了,否则就一直沿重链往上跳。
先跳深度深的233
配合线段树等数据结构食用更佳。
优势:log常数很小233
然后树剖可以和dfs序混用,这样我们就可以同时处理链问题和树问题了。
BZOJ4034模板题,表示来之前已经做了几道模板了,水题水题。
BZOJ2243染色(LG有):
给定一棵有n个节点的无根树和m个操作,操作有2类:
1、将节点a到节点b路径上所有点都染成颜色c;
2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),
如“112221”由3段组成:“11”、“222”和“1”。
还是比较简单的题。
考虑在区间维护这个信息。
在线段树的区间上维护区间段树/最左最右颜色。
考虑到线段树上链顺序和链顺序可能是反的,所以可能要特殊考虑一下。
ZJOI2011(BZOJ2325权限题)道馆之战:
给出一棵n个节点的树,每个点上有A区域和B区域,相邻两个相同标号区域可互相到达。
一个点的AB区域可以互相到达,每个区域可能有障碍物,人只能在无障碍区域走。
q次操作:1.修改某个点上两个区域障碍物情况
2.给出xy,询问仅保留x到y最短路径上的点,从x的任一区域出发,
不重复经过同一区域,最多能经过多少区域。
首先这题放到序列上就是一个弱化版的堵塞交通(之前讲了),因为不会绕环
如区间[l,r]记录四个角之间两两互达到情况/互达情况下的最大距离,
以及四个角分别出发能走的最大距离。
所以用树剖就行了,然后一条链就是log个区间,再把log个区间信息合并。
合并枚举中间走的是哪一条边。
但是写起来十分麻烦,因为可能还要考虑区间翻转。
虚树:选择一些点,只将连接他们的最短路径保留,
但点只保留这些点和他们的LCA构成一棵虚树。
也就是说每条路径是一条长链。
虚树的求法是这样的:
dfs一遍得到dfn,将这些点最左的一条链拿出来。
每次拿最靠左的一个端点(即dfn最小),和最左链求LCA,
如果LCA是最左链,那么将这条链延长,
否则弹出最左链这个节点,然后将LCA-当前节点设为最左链。
引用别人博客的:
我们先再次把所有点按dfs序排序,然后我们考虑这样一种方法,我们维护一个栈,
如果栈为空,直接加点,否则我们看栈顶的点是不是当前点的祖先,
如果不是,那么弹出栈顶继续,否则就可以把当前点加进栈了(然后从栈顶向这个点连一条边)。
因为之前插入的点可能是和当前点是并列关系,
这个不一定会需要用到lca,因为我们记录一个dfs时入队和出队的顺序就可以了。
(第一个点就直接作为根了)。
BZOJ3589 动态树(权限题)
给定一棵以1为根的有根树,每个节点有点权,两个操作:
1.子树点权+x 2.求一些链的并集的点权和。
SOL1:
询问时,首先建出所有链的端点构成的虚树。
利用树上前缀和可以计算出每条边是否计入答案,然后树剖计算链和。
SOL2:
然而树链剖分并没有什么用,树剖只是为了求直链的点权和。
直链的点权等于两个端点到根的点权和相减。
不妨维护每个点到根的路径和,加操作就是对x子树内的点p,
加上c*(dep_p-dep_x)=c*dep_p-c*dep_x。
树链剖分:
没有子树操作就看看在区间上怎么做,然后套树剖。
有子树就按照dfs建树,然后转化为区间操作。
轻链暴力:
注意到在剖分后,任意两点路径之间有log条轻边。
在一个点权的问题中,若点权与这个点的出边有关(入边一般为父亲)
那么可以先将这个点的点权赋为重儿子出边的权值,
在树剖过程中遇到轻边再暴力计算这个点点权,2*log个点要暴力改,
复杂度nlogn*calc()
*Flow(放在这是为了展示树剖特殊用法)
给出一个n个点m条边的无向图,每条边有边权,每个点至多属于一个环。
1:修改一条边边权 2.询问x到y的最小割。
冷静分析:woc仙人掌?溜了溜了。但每个点至多属于一个环,感觉可做。
考虑到最小割的情况可能是切了环里的两条边,或一条路径上的边。
仔细思考:把环缩起来,就形成了一棵树。
两个点之间的路径上的边取min,这个很好做,用树剖维护一下就好。
现在每个点有一个点权,这个点权根据路径的变化会有不同。
如果缩点后树剖,对于路径上一个不是重链端点的点,这个点的点权是确定的。
而成为重链端点的点数是logn的。
发现解法:接下来考虑如何求点权。
对于每一个环维护一个线段树。
在树中先把一个点的点权赋为从他的重儿子进来,从父亲出去时的权。
在通过树剖求一整段权值时,需要对每条链点头和为进行单独处理。
两点的LCA也要单独考虑。
讲点分治,好虚啊。虽然分治我还是会一点的,但点分有点难。。。
重心:在一棵树中,和该点相连的连通块中,最大连通块siz最小的点。
容易发现和它相连的点的siz不超过n/2
一棵树中可能有多个重心,当然可以理解为重心在边上233.
事实上没什么所谓,我们只需要找到一个点使得去掉它后最大一个子树不超过一般。
所以像树剖一样随便找个根,记录siz,
然后每个点删去后的最大子树就是儿子的max和父亲子树中较大的一个。
O(n)
那么分治的过程,就是每次找一个重心,将这颗树分成两块。
所有边界点就是分割的点,即之前访问过的节点。
这样我们dfs子树重心的时候就是找到之前访问过的点停下就行了。
特点:每次都把树分成了若干个连通块
每条路径恰好出现在一个连通块中,在路径统计问题上有优势。
BZOJ1468 Count(POJ1741):给出一棵树,求有多少点对的距离<=k
经典裸题,但是是权限题,也比较简单。
我们在每一个递归结构里,计算出到重心的距离为d的有多少个。
这样我们可以快速计算有多少点对经过重心到达距离为k。
令f[i]为到根距离等于i的有多少个点,然后再求个前缀和。
这是可以用sort+线性扫描前缀和搞定的(其实并不用sort)
有些路径不经过重心,而这些路径来自同一个子树,
因此我们再对每个子树做一次,去掉不合法路径。
通过这个拓展我们还可以求出这颗树上第k小的距离
K-th Path
给出一棵带权树,求点对距离中的第k小,3w级别。
直接求解第K小有些困难,不妨考虑二分最后的答案。
现在的问题就转化成与上一题一样–求解小于等于二分值的
路径条数。
由于带权,不能直接开数组存下来,但是一个结构中的不同
数字总数是O(size)的,考虑离散化之后进行之前的操作。
总复杂度O(N log2 N log Ans).
每次二分完之后我们要离散的东西都是完全一样的,对时间
复杂度未免太过浪费,可以考虑先预处理一次每个结构把排
序预先排好,二分后询问只需要直接调用之前的结果。时间
复杂度O(N log N log Ans + N log2 N)
点分树:就是把点分治中每个重心的父亲设为他上一层的重心,
并在重心出记录下这个连通块所有的重心。
由于整个结构总点数是nlogn级别,记录信息也是nlogn级别。
记录信息时可以在点对编号前多加一维是在第几层的信息方便使用。
好处:
如果每次只询问一条路径,就不用每次重构点分树。
两个点的路径在点分树的LCA出恰好算到一次。
可以支持一些简单的修改操作。
动态点分好(入门)题:
!CF LWDB(http://codeforces.com/gym/100633/problem/D)
给出一棵带边权的树,有两个操作:
1.把距离x这个点不超过d的点全部都染成颜色c,初始所有点颜色为0.
2.询问点x的颜色。
N, Q ≤ 100000.
注意到本题不需要计数,也就是说重复打标记也不会有什么问题。
又两点的距离在不是恰好分离的其他结构中都远于这两点的距离。
不妨在每个结构的重心上维护一个单调队列,
记录的是随着时间的递增,
到了重心之后还能继续扩展距离递减的覆盖操作的单调队列。
在修改的时候,考虑在点分树上跑,
所有包含这个点的结构就是从这个点以及他的所有祖先。
询问时也从这个点开始向上跑,
在每个祖先里求出能到达这个点的最后一次操作,取最晚的一次操作即可。
由于在询问时要使用二分,总复杂度为O(N log2 N)
BZOJ1095捉迷藏(经典题,终于不是权限题了,然而是省选的- -,怪不得)
给出一棵树,每个点有黑色和白色两种颜色。要求支持以下两种操作:
1.翻转某个点的颜色
2.求最远黑色点对的距离
N ≤ 100000, M ≤ 500000
这题是可以用线段树做的,不过我们先讲点分吧。
要维护全局最长路,我们可以维护每个结构中经过重心的最长链。
修改一个点的颜色最多修改log个结构的答案,沿点分树往上跳。
记录每个点是在多少层,属于哪一个子树,就很好维护答案了。
在每个重心位置维护一个set,表示每个子树里最大链长度。
对每个子树维护一个set,表示所有黑点距离合集。
再对全局开一个set,维护所有重心的答案合集。
果然stl真好用23333,但是这题我并不能搞出来,因为没什么听懂。
复杂度O((n+m)log^2n)
Codechef BTREE——Union on tree
给定一棵n个节点的树,树的每一条边的长度都是1。
接下来有Q组询问,每 组询问给出了Ki个数对(ai, ri),
表示距离点ai在ri以内的所有点都被守护了,问一共有多少点被守护了。
n, Q ≤ 5 * 10^4, ∑ Ki ≤ 5 * 10^5
先看看每次Ki = 1怎么做。
当只有一个点时,需要询问距离某个点u<=r的点的个数.
考虑在点分树里的每一层,那么就是刨去这个点所在的子树
之后,剩下的点里距离重心x不超过r-dis(x, u)的点的个数,而这可以预处理好。
总复杂度O((N + Q) log N)
那么Ki > 1,考虑到Ki的和很小,不妨对这Ki个点构建虚树。
考虑虚树上任意两个点a,b,如果他们的距离为d,且b的守护范
围为c,则b对a的贡献就是c-d.
通过一个类最短路算法就可以算出每个点的真实守护距离。
既然都写了一个点分,那么必须利用上才行。
考虑一个最简单的容斥,加被至少一个覆盖的,减被至少两个覆盖的。。。。
2^Ki项?多点范围求交?
NO!只需要减去虚树上相邻两项的值就行了
为什么呢?我们将这个虚树剖壳,
对于任意两个点,我们将两点路径上的所有点去掉,
全部去掉以后就是一个外壳。
然后可以证明,对这个外壳求交与对原虚树求交是一样的。
怎么证明呢?考虑容斥原理和杨辉三角什么的,反正我是不会。
如何求相邻两个点的交?
首先我们可以证明,一定有一个位置使得两个点到这个点后的剩余距离相同。
当然这个位置可能是在边上的233,所以我们就可以在这个位置上塞一个点进去。
我们把c塞在中间就行了。
这样最后的结果在这个位置上求一次就行了。
然后讲了一个点分与FFT的东西
但是我不会FFT,听了这6h的课以后我发现我也不会写点分了。
心态是有点炸的啊。
FFT是一种可以在O(N log N)的时间内高效计算卷积的算法。
有时候我们会在树上遇到和路径长度相关的计数问题
一个简单的例子:在一棵边权都是1的树上计算长度为k的路径条数,对每个k都求。
类比求一个k的做法,在每一个重心上计算经过这个重心的路径。
再次利用长度和size同级别的性质,在O(size log size)的时间内计算出答案。
又size的总和是N log N,总的时间复杂度为O(N log2 N),与分治FFT相同
可以看这个东西:Codechef primedist
果然CC的题都很难嘛,还是CF比较善良(CN round就算了
话说FZ说有一种O(n)的最短路算法,感觉奥妙重重。
不过这种算法在n为2^(10^21)左右常数才看不见,才“跑得飞快”
EXM?要你何用?
然后讲LCT,我已经虚炸了啊。
啊啊啊啊啊啊啊啊啊,都在码之前的dp题,颓的一批。
还是认真听LCT吧。
动态树是一种对树链剖分的加强,可以支持对树的形态做简单的修改,
并且维护一些简单的链上信息。
与树链剖分相同,每个点也有自己的重儿子,只是随着操作的进行,
我们需要改变每个点的重儿子,并且还要维护每条重链的信息。
在树链剖分里,我们使用了线段树来维护重链信息。
事实上也可以用splay来维护,但是为什么不用splay呢,理由是splay太慢了,
而且静态操作中对区间的拆分也没用要求。
但在LCT中,splay就能够大显身手了。
重儿子:重儿子与父亲节点同在一棵Splay中,一个节点最多只能有一个重儿子
重边:连接父亲节点和重儿子的边
重链:由重边及重边连接的节点构成的链
在LCT中,我们利用splay来维护每一条重链。
在splay中按中序遍历遍历出来的,恰好就是这条链自顶向下的顺序。
同时,为了方便维护树的结构,我们将每棵splay根的父亲设为这条链链顶的父亲。
Access:
LCT最基本最精髓的操作之一。(划重点.jpg)
把x到根的这整段路径都变成一条重链,然后断开其他连着这条重链的重边(包括x).
根是哪个?注意到我们之前记下了每条重链的父亲,
不停往上跳直到一个没有父亲的splay里中序遍历最小的那个就是啦。
这样做了以后会发生什么?x到根这条路径就在一个splay里辣,
这样我们就取出了一条路径(但是现在只能取出到根的路径)。
那么怎么Access呢?首先找到x所在的splay,然后把比x顺序大的点切掉。
由于所有重链都是直链,利用记录的轻链fa找到上一棵splay,
并且把比fa顺序大的点都切掉,再把两棵splay接起来,
继续往上跳直到根为止。得到的splay就是我们需要的了。
关于切掉:要切掉中序遍历比x大的点,
可以把x splay到根上,
然后把x的右儿子直接赋成要接上来树的根,
这样做不仅切掉了点,还顺便维护了轻边的信息。
光有access只能求到根的路径,感觉并没有什么卵用.
如果能换根的话,就能提出每一条路径了。
事实上把根换成x只需要access(x)之后把这棵splay翻转一下就行了。
为什么?其他splay在换根后的树里其实并没有改变顺序,也没有改变父亲。
有了Access以后,Link&&Cut就很容易了。
Link的话,把x变成x所在树的根,然后把x的父亲指向y。
Cut的话,由于x和y相邻,把x变成根,然后把y access一下,然后把x, y断开就行了。
LCT的时间复杂度O(N log N).
所有操作中只有Access需要计算次数。
通过势能分析可以求得Access中splay的总复杂度是N log N的。
一般做题只要记得复杂度是O(N log N)就行了。
但要注意这并不意味着Access切换边的次数是O(N)的,可能是NlogN。
[BZOJ2002]弹飞绵羊
LCT基础练习题,考虑建立一个点n + 1表示绵羊被弹飞,
每个点连向下一个能飞到的点。那么答案为这个点到n + 1路径上的点数。
利用LCT的link, cut,维护splay中的size即可。
时间复杂度O(N log N)
[Codechef]GERALD07,Chef and Graph Queries
这题在讲图论的时候讲过了233,1.16下午的,一笔带过了。莫队/LCT
这里当然是用LCT辣:
考虑离线,从小到大枚举询问的右端点。
回忆一下求最大生成树的过程,每次添最大的边看会不会成环。
那么对于[Li, Ri]这个询问,答案就是n减去以[1, Ri]构成的最大生成树里,>=Li的边的条数
LCT动态加边求最大生成树又是一个非常经典的问题。
加入一条边的时候,如果没成环就可以直接加入。
判断是否成环:把x当根,y access一下,看看y是否在x的splay里。
否则,需要删去x, y路径上的最小边。
如何在由点构成的树上维护边权——在边中间加入一个点代表边,
又是一个经典做法,牺牲常数换简易度。
时间复杂度O(N log N)
[IOI2011]Dancing Elephants
有N只大象在一条数轴上,第i只大象位于位置xi,现在需要拍摄这N头大象。
你的摄像机一次只能拍摄一段长度为L的区间,
你想要求出最少拍多少张照片才能拍到所有大象。
有M次移动事件,即某只大象改变了它的位置,你需要在每次改变后求出最少拍摄次数。
1 <=N, M <=150000.
先考虑一次操作怎么做,从左往右贪心即可。
回忆一下谭飞绵羊0.0
把所有可能有大象的位置离散出来。
一个点x如果有大象,那么可以把它向第一个>=x+L的点连边。
否则把这个点向后一个点连边。
容易发现贪心的答案就是从最左侧的点出发经过经过路径中有大象点的个数。
把有大象的点点权设成1,否则为0,问题就是路径上的权值和了。
复杂度NLOGN
UOJ#207. alpq大爷游北京(原名共价大爷游长沙,被老师改了。)
北京市的道路可以抽象成一棵n个点的树,每个周某勤劳的alpq大爷都会游历北京市。
有一个包含一些点对(x, y)的集合S,alpq的线路是在S中选择一个点对(x, y)并从x走到y.
作为alpq大爷的脑残粉,小G决定在某条边上等着alpq来,
为了保证一定能见到他,小G会选择一条alpq一定会经过的边。
当然,由于施工问题,某个时刻某条边会断开,另一条边会连上,同时保证仍然是一棵树。
alpq大爷也可能是在S里增加点对或者删去点对。
在每次操作后,请你回答小G如果他在(ai, bi)这条边上等,是否一定能见到alpq大爷。
N, M <=100000
在讲这个东西之前,我们先要知道一个东西
Xor Hash:
给每个物品随机一个值,通过两个集合的xor和来判断是否相同。
Xor好处都有啥:容易合并。
Xor两次以后就消掉了233,出现两次就不见了TAT。
如果没有link - cut操作,可以直接采用树链剖分来维护每条边上经过了多少点对。
当然,我们也可以使用Xor hash,询问的时候只要询问这条
边上的xor和与所有边的xor和是否相同就行了。
带上link-cut之后,统计边数就不太可行了。
但是可以发现,删掉一条边加入一条边等价于把删去边的权值xor这个环的另外一段上。
总复杂度O(N log N).
[Codechef]MONOPLOY
给定一棵n个节点的有根树,开始每一个点都有一个不同的权值,接下来进行m次操作:
1. 把i到根路径上的所有节点标记成一种新的权值。
2. 询问i子树中所有节点到根路径上不同权值个数的平均值。
n, m <= 2 * 10^5
对于操作1:
看到给到根路径赋一个新颜色,有没有想到什么?
没错就是LCT的access操作。
也就是说均摊会覆盖的不同颜色段数是O(N log N)的.
对于操作2:
由于是到根路径覆盖,每条链留下来的一定是一段后缀。
又我们求的也是到根路径,所以每条链的链顶是最厉害的的。
维护每条链的顶端。求平均值可以转化为求总和除以个数。
每种颜色覆盖的点就是dfs上的一个区间加.
用dfs序+线段树就可以解决了。
如何维护链的顶端:可以直接用一棵LCT。
时间复杂度O(N log2 N).
“学”了LCT,回过头来看早上的FLOW,题目看笔记AM
把之前的做法搬上来,嗯好像能做
但是代码和时间复杂度…似乎没什么改变呢
既然有了LCT,就要再分析一下题目性质
维护边上的边权肯定是没法更优了,考虑维护每个环的值。
一个环里分成的两条路径上,一定有一条经过最小值。
由此可见最小值这条边一定在这个环的割集里。
那我们为什么不把最小边直接割了呢?
如果割去最小边并把这个环上的点还原出来,
我们发现任意一条路径的另外一段就是拆开后两点间的路径。
那么我们可以把最小值加到这条路径的其他边上。
这样就询问只要求个树上路径的最小边权值就行了。
而修改一条边权,如果它变成了最小边权,
那么就是把原来的边link回来,再把这条边cut掉,同时要进行两次路径加。
否则就是单点修改。
今天也水了几道题,算一算貌似屯了15+已切题没有写总结,屯了15+题没有做。
看看什么时候写掉题解好了。
路漫漫其修远兮!
听说可以进竞赛有为班2333,躺着进,躺着被dalao们虐出来。