zoukankan      html  css  js  c++  java
  • 静态链分治进阶

    静态链分治,另称 ( ext{dsu on tree}) ,是一种维护子树信息的强大工具。

    但实际上它能做的并不局限于子树信息;

    问题引入:

    给定一棵树,每个点有三个权值 (a_i,b_i,c_i)
    对每个 (i)(w_i=Big|ig{j|a_j+b_{operatorname{lca}(i,j)}=c_iig}Big|)

    这个问题有一个基于 ( ext{dsu on tree})(O(nlog n)) 做法。

    先明确在处理每个点 (x) 时要求出 (operatorname{lca}(i,j)=x) 的全部点对 ((i,j)) 之间的贡献。

    这样前面的限制有了好看的形式: (a_j+b_x=c_i)

    那以朴素的 ( ext{dsu on tree}) 的做法,可以将这些点对分为两部分。

    (1.) (i)(x) 的轻儿子 (y) 子树内的点,(j) 为除 (y) 外其他全部儿子子树内的点

    这时为了统计 (w_i) ,可以让所有的 (a_j) 出现的次数开个数组 (cnt) 统计。

    而统计 (cnt) 是天然的一个子树信息,可以在 ( ext{dsu on tree}) 时同时处理出来 (x) 子树内的点的贡献。

    但是为了 (j) 不在 (y) 的子树内,可以先将 (y) 子树内的点对 (cnt) 的贡献减去,查完后再加回。

    由于 (x) 固定,每个 (y) 子树内的点 (i) 直接查 (cnt_{c_i-b_x}) 的值即可。

    这部分的时间复杂度与 ( ext{dsu on tree}) 原先复杂度相同,是 (O(nlog n))

    因为都是要将轻儿子子树内全部点统计,重儿子的 (cnt) 信息可以直接利用。

    (2.) (i)(x) 的重儿子 (son_x) 子树内的点,(j) 为轻儿子子树内的点

    这可以在查轻儿子子树内的点时,考虑对重儿子子树内的点的影响。

    对一个轻儿子子树内的点 (j) ,这个影响就是(son_x) 子树内的,(c_i=a_j+b_x)(i) 点的 (w_i) 加一

    如果没有在 (son_x) 子树内的限制,直接再开个桶,在每个 (c_j+b_x) 处加一,最终对每个 (i) 统计 (c_i) 被加了多少次。

    但即使加上 (son_x) 子树内的限制,也可以转化到 (dfs) 序上用这个方式实现。

    这就可以把之前 (c_j+b_x) 的贡献在 (dfn_{son_x}) 时加一,在 (dfn_{son_x}+size_{son_x}) 时减一。

    从小到大枚举 (dfs) 序,依次统计 (dfn) 所对应的点,作为 (i) 被加的贡献。

    由于 ( ext{dsu on tree}) 所带来的这样的操作数是 (O(nlog n)) 的,所以这部分时间复杂度同样是 (O(nlog n))

    一些应用

    约定:位置 (i) 对应的 ( ext{SAM}) 中的点为 (rev_i)( ext{SAM}) 上的点 (x) 对应原序列位置为 (pos_x)。(克隆节点 (pos)(0)

    ( ext{U186306})

    由于要求 (operatorname{lcs}(s[1dots j],s[1dots i])=s[j+1dots i]),考虑扔到 ( ext{SAM}) 上的 ( ext{parent}) 树。

    那原题中 (w_i=Big|ig{y|pos_y+len_{operatorname{lca}(x,y)}=pos_x,x=rev_iig}Big|)

    这直接套用上面方法,完全一样。

    ( ext{P1117})

    枚举断点,设每个位置 (i) 作为 ( ext{AA}) 的右端点的值为 (f_i)

    转化到 ( ext{parent}) 树上就有 (f_i=Big|ig{y|pos_y+[1,len_{operatorname{lca}(x,y)}]=pos_x,x=rev_i ig}Big|)

    这有别于上面,因为 ([1,len_{operatorname{lca}(x,y)}]) 是一段区间而非一个值。

    但大体上没有太大差别,其实把之前用的桶 (cnt) 改成一个树状数组即可。

    • 对于上述第一种情况,仍然是把 (pos_y) 插到树状数组内,查询 ([pos_x-len_{operatorname{lca}(x,y)},pos_x-1]) 的和。

    • 而第二种情况变成一段 (dfs) 序内的点,将 ([pos_y+1,pos_y+len_{operatorname{lca}(x,y)}]) 的全部值加一或减一。
      (dfs) 序枚举每个点,查询 (pos_x) 当前被加了多少。

    而作为 ( ext{BB}) 的左端点的值 (g_i),可以将序列反过来以同样的方法再求一次。

    最终统计 (sumlimits_{i=1}^{n-1}f_ig_{i+1}) 即可,时间复杂度是 (O(nlog^2n))

    ( ext{P5161})

    可以将原序列差分后,转化为查询不相交也不相邻的子串对数。

    对一个 (r_1),考虑其对各个位置的 (r_2) 的贡献:

    • 也就是说一个 (r_1)(r_2) 的贡献是一个二维前缀和的形式,

    • 反过来,对一个 (r_2)(r_1) 的贡献为 (egin{cases}r_2-r_1-1,r_1in[r_2-len-1,r_2-2]\len,r_2in[1,r_2-len-2]end{cases})

    • 对每个轻儿子子树内的 (r_2),按上式算出需要区间内的 (r_1) 的个数与和,可以用树状数组实现。

    • 对重儿子的贡献需要 把能影响 (r_2)(r_1) 统计出二维前缀和,依然能用树状数组。

    ( ext{P4482})

    设查询区间为 ([l,r])

    (x=rev_r) ,则左侧最长 ( ext{border}) 的右端点即为 (w_i=mathop{mathrm{Max}}limits_{y} pos_y[pos_y<r][pos_y-len_{operatorname{lca}(x,y)}+1leq l])

    最终如果 (w_i<l) ,则表示不存在 ( ext{border})

    • 对于第一种情况,可以在一个数据结构中将 (pos_y) 插到 (pos_y) 的位置上,
      然后查询位置在 ([1,min(l+len_{operatorname{lca}(i,j)}-1,r-1)]) 中的最大值。
      但由于按之前的思路,同时要支持删除某位置上的数的贡献,而 (pos_y) 互不相同,可用线段树实现。
      这部分时间复杂度为 (O(nlog^2n))

    • 对于第二种情况中,轻儿子对重儿子的贡献,要保证 (pos_y<r,pos_y-len_{operatorname{lca}(x,y)}+1leq l)
      那用一个树状数组套线段树可以实现。
      要注意 (pos_y-len_{operatorname{lca}(x,y)}+1) 可能重复,可以对内层线段树下标扩大一些。
      由于 ( ext{dsu on tree}) 会带来 (O(nlog n)) 个这样操作,加上树套树要 (O(nlog^3 n))

    • 但事实上这部分还可以不用树套树,仅用一个线段树。
      将每个 (pos_y) 为下标查到线段树中,对线段树的每个节点,记录这个区间内 (pos_y-len_{operatorname{lca}(x,y)}+1)最小值
      这样就能在线段树上二分了,在保证位置 (<r) 且区间的这个最小值 (leq l) 前提下,尽量往右查。
      最终查到的位置,就是最大 (pos_y)
      但操作中存在删除,可以在叶子节点上开个可删堆查动态最小值,总时间仅为 (O(nlog^2n))

    但这并不代表树套树没用,它能解决一个更强的问题:

    ( ext{U186697})

    仍然设 (x=rev_r) ,则答案为 (sumlimits_{y}[l+k-1leq pos_y<r][pos_y-len_{operatorname{lca}(x,y)}+1leq l])

    然后发现这用上面的方法几乎完全一样。

    • 第一种情况在甚至不用线段树,直接树状数组,在 (pos_y) 的位置 (+1)
      然后查询的范围为 ([l+k-1,min(l+len_{operatorname{lca}(x,y)}-1,r-1)]) 的和。
      这部分仍然是 (O(nlog^2 n))

    • 第二种情况,重儿子的贡献的限制为 (l+k-1leq pos_y<r,pos_y-len_{operatorname{lca}(x,y)}+1leq l)
      由于是查询个数和,线段树上二分的方法不再可行。
      所以仅能树套树,时间复杂度为 (O(nlog^3n))

  • 相关阅读:
    社交需求和社交产品的更替
    腾讯产培生面经
    【C++基础】类class
    【C++基础】结构struct
    【C++基础】C-串知识整理
    GeoServer war包在tomcat7中配置遇到的一个问题
    pgrouting 2.0 的环境配置
    阿里2014年9月笔试中的一个算法设计题--擦黑板剩余数字
    VisualSVN Server的启动关闭脚本
    二叉树遍历(前序、中序、后序)的递归及非递归实现(小结)
  • 原文地址:https://www.cnblogs.com/Y-B-X/p/15484639.html
Copyright © 2011-2022 走看看