• 长链剖分学习笔记


    洛谷日报

    作为树链剖分中的一种,长链剖分远没有重链剖分和实链剖分常用,于是仅作简单了解。

    长链和长儿子

    一个点的所有儿子中能够引出深度最深的那个点为这个点的长儿子。由长儿子关系组成的链称为长链。

    void dfs_son(int cur, int faa) {
      dep[cur] = dep[faa] + 1;
      int mx = -1;
      for (register int i = head[cur]; i; i = e[i].nxt) {
        int to = e[i].to; dfs_son(to, cur);
        if (mxlen[to] > mx) mx = mxlen[to], son[cur] = to;
      }
      mxlen[cur] = mx + 1;
    }
    void dfs_chain(int cur, int topp) {
      top[cur] = topp;
      if (!son[cur])  return ;
      dfs_chain(son[cur], topp);
      for (register int i = head[cur]; i; i = e[i].nxt) {
        int to = e[i].to; if (to == son[cur]) continue;
        dfs_chain(to, to);
      }
    }
    

    性质

    一个点到根的路径的虚边数为 (O(sqrt n))

    (证明可以考虑经过一条虚边就加了个 (len) 的链,最劣时子树大小为一等差数列)

    长链剖分的 DFS 序

    与重链剖分类似,长链剖分也能在 dfn 上搞些事情,这集中体现在开空间上。如果每个点都开深度大小的空间,时空复杂度爆炸,无法做到 (O(1)) 继承重儿子信息(似乎 vectorswap 可以,没试过),于是可以直接让一条链的信息都维护在 DFS 序上的一个固定的区间中,方便直接继承。

    当然用指针也可以。

    应用

    树上 k 级祖先

    众所周知,倍增可以在线解决树上 (k) 级祖先的问题,复杂度为 (O((n + q)log n))

    还有一种离线的方法,把询问挂点上,DFS 树并记录当前链,查询就直接在栈上查。复杂度 (O(n+q))

    然后到了树链剖分显神威的地方了。重链剖分能够做到在线 (O(n + qlogn))。具体来说就是如果发现可以跳链就跳,如果目标在链上就直接在 DFS 序上查。

    长链剖分能做到在线 (O(nlogn + q))。先长链剖分,每条链用 vector 记录链上的 (len) 个点以及链上方的 (len) 个点。顺便求出倍增数组。每次询问首先跳到 (log_2L) 级祖先,然后在当前链中查询目标点。当前链一定储存目标点,因为当前链深度至少是 (L/2)

    Dominant Indices

    显然可以用 dsu on tree 来做,但是用长链剖分可以做到 (O(n))(尽管非常慢)

    仍然类似 dsu on tree 的做法,只不过这次先去长儿子,然后继承长儿子的信息,暴力合并其他儿子的信息。暴力继承是不行的,我的做法是直接记录在长链的顶点上。

    每条链最多只会被合并一次,因此复杂度为 (O(n))

    重链剖分不行,因为可能轻儿子比重儿子更深,导致重儿子的记录的深度被迫加长。

    HOT-hotel

    可以称作长链剖分优化DP以及动态开空间的模板了。

    (f(p)(i)) 表示 (p) 节点子树中距离 (p)(i) 的节点个数,(g(p)(i)) 表示 (p) 子树中已经选好两个点,且需要从子树外找一个距离 (p)(i) 的点的点对数。那么有:(大写为父亲,小写为儿子)

    [Ans gets F(i)g(i+1)+G(i)f(i-1) ]

    [F(i) gets f(i-1) ]

    [G(i) gets F(i)f(i-1) + g(i+1) ]

    直接暴力能做到 (O(n^2))

    发现 DP 数组下标有关深度,那么一个套路就是用长链剖分来优化。重儿子直接继承,轻儿子暴力合并。需要在 dfn 上开数组,并且由于涉及到数组的左移右移,可能需要开二倍(一个点占用两个位置)。

    关键代码:

    int dfn[N], dcnt, top[N];
    void dfs_chain(int cur, int topp) {
    	top[cur] = topp;
    	++dcnt; dfn[cur] = ++dcnt;
    	stf[topp] = dfn[topp] + mxl[topp], stg[topp] = dfn[topp];
        ...
    }
    
    void dfs(int cur) {
    	if (!son[cur]) {
    		f[stf[top[cur]]] = 1;
    		return ;
    	}
    	for (int i = head[cur]; i; i = e[i].nxt) {
    		int to = e[i].to; if (to != fa[cur])	dfs(to);
    	}
    	ans += g[stg[top[son[cur]]] + 1];
    	f[--stf[top[cur]]] = 1;
    	++stg[top[cur]];
    	for (int i = head[cur]; i; i = e[i].nxt) {
    		int to = e[i].to; if (to == fa[cur] || to == son[cur])	continue;
    		for (int j = 0; j <= mxl[to]; ++j)
    			ans += 1ll * f[stf[top[cur]] + j] * g[stg[to] + j + 1] + 1ll * g[stg[top[cur]] + j] * (j ? f[stf[to] + j - 1] : 0);
    		for (int j = 0; j <= mxl[to]; ++j)	g[stg[top[cur]] + j] += g[stg[to] + j + 1];
    		for (int j = 1; j <= mxl[to]; ++j)	g[stg[top[cur]] + j] += 1ll * f[stf[top[cur]] + j] * f[stf[to] + j - 1];
    		for (int j = 1; j <= mxl[to]; ++j)	f[stf[top[cur]] + j] += f[stf[to] + j - 1];
    	}
    }
    

    WC2010 重建计划

    这里长链剖分的套路越来越像重链剖分了。

    发现DP数组的转移又是左移右移,上 DFS 序;查询时需要查区间最值,上线段树维护 DFS 序。

  • 相关阅读:
    C#基元类型、引用类型和值类型
    UML类图中泛化、实现、依赖、关联、聚合、组合关系
    简述:聚集索引和非聚集索引的区别
    面向对象编程的三特性、七原则和六视点
    设计模式学习笔记——解释器模式(Interpreter)
    设计模式学习笔记——组合模式(Composite)
    程序员编程利器:20款最好的免费的IDEs和编辑器
    奇技淫巧之浏览器秒秒钟变编辑器
    前端技术Jquery与Ajax使用总结
    Chrome也疯狂之Vimium插件
  • 原文地址:https://www.cnblogs.com/JiaZP/p/13648070.html
Copyright © 2020-2023  润新知