关于树上信息的维护(持续更新因为后面没听懂(逃))
1.Dfs序的一些奇奇怪怪的操作和改进
所谓dfs序,就是一棵树被 dfs 时所经过的节点的顺序。一般的作用是维护子树信息,如果记录 dfn[i] 表示 i 号点的 dfs 序,sze[i]表示 i 号点的子树大小,那么 x 是 y 的祖先等价于:dfn[y] ∈ [dfn[x], dfn[x]+sze[x]−1]
那么我们就可以利用dfs序来进行一些树上的操作:
1.维护一棵树,支持:子树加,链加,单点求值。
对于子树加,相当于是 dfs 序列上的区间加。
对于链加,可以看作是一个点到根上的路径加。
可以把这次的修改放到这个点上,然后单点查询的时候在子树内区间询问贡献。
2.维护一棵树,支持:子树加,链加,子树求和。
对于子树加,相当于是 dfs 序列上的区间加。
对于链加,可以看作是一个点到根上的路径加。
一个修改 (x,W) 对 y 有贡献当且仅当 y 为 x 的祖先。且贡献为(dep[x]−dep[y]+1)∗W。
分离变量,即为dep[x]∗W+(1−dep[y])∗W。
所以维护两个区间和就行了。
3.维护一棵树,支持:单点加,链求和。
对于链求和,可以看作是一个点到根上的路径求和。
一个修改 (x,W) 对 y 有贡献当且仅当 x 为 y 的祖先。
相当于支持区间加,单点求值。
4.维护一棵树,支持:子树加,链求和。
对于链求和,可以看作是一个点到根上的路径求和。
一个修改 (x,W) 对 y 有贡献当且仅当 x 为 y 的祖先,且贡献为(dep[y]−dep[x]+1)∗W。
分离变量,即为(dep[y]+1)∗W−dep[x]∗W。
维护两个区间和即可。
5.维护一棵树,支持:子树加,链加,链求和。
子树加对于链求和,可以看作是一个点到根上的路径求和。
关于链加,(x,W) 对 y 的贡献分开讨论:
(1)若 x 为 y 的祖先,那么贡献为 depx ∗W。
(2)若 y 为 x 的祖先,那么贡献为 depy ∗W
分开维护贡献即可。
然后将这些东东结合起来,就可以出一些毒瘤题了(逃)~
关于dfs序的一些小改进
1.括号序列
考虑 dfs 序,在 dfs 完一个点的时候,我们同样把他加入序列,这样的得到的序列叫做括号序列。对于一棵有根树,
如果子节点有序,那么这棵树唯一对应着一个括号序列。
例如下图:
这棵树的括号序列就是1233442551,我们发现,如果用左右括号来代替进与出,那么整个序列就可以变成( ( ( ) ( ) ) ( ) ),由这个序列我们可以还原出这棵树及其dfs序,由此可见,括号序列与树是唯一对应的。
这里贴一道例题:BZOJ4337 树的同构,传送门:https://blog.csdn.net/g21glf/article/details/82952739
然后再口胡一道例题:
给定一棵树,支持给某个点换父亲,子树修改,查询某个点子树点权和。
题解:结合括号序列,可以发现,一个子树一定在他的根节点的括号内,那么对于换父亲,就是将一段括号从一个括号内移动到另一个括号内,用平衡树来维护区间的左括号和,支持区间挪动,区间加即可。
2.欧拉序
考虑dfs序,在dfs完一个点的某个子树的时候,我们把他加入序列,这样得到的序列叫做欧拉序,还是上图:
这棵树的欧拉序就是12332441551,然而看了是不是很头大?没关系,咱们随便在树上找两个点,看看他们和他们的lca在欧拉序中的位置关系,没错,我们发现两棵不同子树之间一定“夹”住了他们的lca,由这个性质我们可以利用st表进行O(nlog n)的预处理,然后就能实现O(1)复杂度求lca了!(然而还是树剖比较简单,下次更新就写树剖)
2.关于树链剖分的一些感受
树和序列其实有相同之处,我们可以把树分成若干个序列来维护。常用的技巧就是树链剖分。
我们把一棵树的边分为轻边和重边,保证每个节点与它的儿子之间只有一条重边,如下图:
其中红色加粗边就是这棵树的重边,具体的,size[u]记表示以 u 为根的子树的结点个数,令v为u所有儿子中size值最大的一个儿子,则(u,v)为重边,v称为u的重儿子。u到其余儿子的边为轻边。
这样每个点都可以沿着重边往下走到某个叶子节点,或者往上走到某条轻边或者根节点。我们可以把这一段信息一起维护。这样这棵树的信息就会被储存在若干条链上,这就是树链剖分的本质。
轻重边有如下几个性质:
1.如果(u,v)为轻边,那么size[v]<=size[u]/2(否则v就是重儿子)。
2.从根到某一点v的路径上最多有log n条轻边(每经过一条轻边,节点数至少缩为原先的一半,最多log次就为0)
3.我们称某条路径为重路径(链),当且仅当它全部由重边组成。那么对于每个点到根的路径上都不超过 O(log n) 条轻边和 O(log n) 条重路径。(因为每条重路径两端都是轻边,最多有log n条轻边,就最多有log n条重路径)
利用树链剖分求LCA
首先,x,y的LCA要么在两个点所在重链上,或者在该重链上方的重链上。
当 x, y不在一条重链上时,dep[top]较小的点所在的重链肯定不是要求的链,我们把这个点向上跳到fa[top]处。
当在一条链上时,深度较小的点为LCA。
因为一个点最多跳O(log n)次,所以时间复杂度为O(log n),而且常数比倍增小。
代码:
#include<bits/stdc++.h> using namespace std; const int MAXN=2e5+10; int n,m,cnt; int head[MAXN],depth[MAXN],fa[MAXN],son[MAXN],siz[MAXN]; int nxt[MAXN],to[MAXN]; void add(int x,int y) { cnt++; nxt[cnt]=head[x]; head[x]=cnt; to[cnt]=y; } void dfs1(int x,int fa) { depth[x]=depth[fa]+1; siz[x]=1; for(int i=head[x];i!=-1;i=nxt[i]) { int v=to[i]; if(v!=fa) { fa[v]=x; dfs1(v,u); siz[x]+=siz[v]; if(son[x]<siz[v]) son[x]=v; } } } void dfs2(int x,int fa) { if(x==son[fa]) top[x]=top[fa]; else top[x]=x; for(int i=head[x];i!=-1;i=nxt[i]) { int v=to[i]; if(v!=fa) dfs2(v,u); } } void init(int root) { dfs1(root,-1); dfs2(root,-1); } int lca(int x,int y) { while(top[x]!=top[y]) { if(depth[top[x]]<depth[top[y]]) swap(x,y); x=fa[top[x]]; } return depth[x]<depth[y]?x:y; } int n,m,x,y; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) { scanf("%d%d",&x,&y); add(x,y),add(y,x); } int q; scanf("%d",&q); while(q--) { scanf("%d%d",&x,&y); printf("%d\n",lca(x,y)); } return 0; }
例题:BZOJ1036,题目+题解传送门:https://blog.csdn.net/g21glf/article/details/82956343
然后就是利用树链剖分进行的一系列操作,待更新。。。
2018.10.7.12:00