W
e
l
c
o
m
e
: )

一些还没熟练的算法

虚树

一个简单的建树方法:

  1. \(dfs\) 序排序关键节点。

  2. 相邻点的 \(\operatorname{LCA}\) 加入到虚树集合中

  3. 对集合中的点按原树中的祖先关系建边

我们发现比较恶心的是第 3 步无法快速实现,考虑将点一个一个插入,维护这个 2-3 的过程。

我们用一个单调栈维护点,满足栈底元素到栈顶元素的 \(dfs\) 序单调递增。

当我们新插入一个结点 \(u\) 时,求出它与 \(top\)\(\operatorname{LCA}=x\)

如果 \(x=top\),那么 \(top\)\(u\) 的父亲,并把 \(u\) 加入到栈中。

如果 \(x\ne top\),说明 \(x-top\) 这条链的虚树结构已经构造完毕,那么一直将栈顶元素弹出并连边 \((top-1, top)\),直到栈顶元素的 \(dfs\) 序小于等于 \(x\) 停止,不断将弹出的元素建边 \((top, top-1)\),那么再把 \(x\) 压进去就可以了(要看此时栈顶是不是 \(x\),因为 \(x\) 在之前可能已经被加过了)。

坑点:最后弹出的是 \(x\) 的儿子,连边是 \((x, top)\),为什么上面是 \(\le\) 而不是 \(<\),因为 \(x\) 是要保留在栈中的,其与 \(top-1\) 的连边后面会连,现在是不用连的。

为什么不加边 \((x, u)\)\((top', x)\) 呢?因为他们会在后面被外链迫使其连边。

最后如果栈里还有元素记得一一 pop 连边。

例题:

[SDOI2011] 消耗战:建出虚树后,设 \(f_i\) 为隔断 \(1\)\(i\) 的子树内的关键点的最小代价,则:

\[f_x=\sum \min{Mn[v], opt*f_v} \]

其中 opt 表示该点是否为关键点,若是则为无穷大,否则为 1。

[HNOI2014]世界树:同理,现在需要考虑两点之间的点集怎么分配,我们令每个关键点有一个支配点集 \(S\),设 \(v\in S_u\),当它变为关键点后,所得到的支配点集必然是 \(S_u\) 的子集。

考虑如下增量构造法:在虚树上选取任意一个关键点为新的树根,维护树上每个点所属的关键点支配集的标号,按虚树上深度从小到大把关键点加入(这样我们能保证加入的点至少已经被分配到正确的父亲)。首先当前加入点 \(u\) 的子树必然都被 \(u\) 支配,否则就是和子树外结点的争夺,那么显然就是 \((u, v)\) 路径上的 1/2 分点,数据结构维护即可。

其他: [HEOI2014]大工程 [SDOI2015]寻宝游戏 [HNOI/AHOI2018]毒瘤

其实难的都不是虚树部分。。

点分治

用于处理一类树上路径统计问题,与树上点对距离有关的问题亦可往这里想

考虑这样一个分治操作,对于当前的树,我们只统计经过根的路径贡献,没有经过根的,我们把根结点拿掉,分治交给儿子去做。

显然这个做法在树为链时会爆炸,考虑优化。

我们知道重心的性质是所有子树大小不超过整棵树的 \(\frac{n}{2}\),于是我们珂以在递归下一层分治时先找子树重心,在递归下去。

这样的话递归层数只有 \(\log n\) 层,每层求重心 \(O(n)\),复杂度 \(O((n+f(n))\log n)\)\(f(n)\) 是统计的复杂度。

伪代码:

void dfz(rt){
  vis[rt]=-1;//标记为删除点
  for(each (rt, v) in G)
      if(vis[v]>=0) calc(v);//处理 v 的子树与前面子树的路径贡献
  Getsize(rt);//如果能在前面的 calc 就能将 v 的子树大小算出就不必做这一步
  for(each (rt, v) in G)
      if(vis[v]>=0) findrt(v), dfz(v)
}

明明 findrt 中已经求了一次 siz,为什么要在下一层再 Getsize 呢?

因为在上一层递归中,我们求得并非是以重心为根的 size,因此要重做一遍。

具体可以看 这篇

显然点分治只能做一次,我们考虑将其动态化,以处理多个问题,

那么便引出了动态点分治————点分树

操作很简单,就是在点分治过程中将上一层的递归重心与下一层的连边,构成一颗新树。

考虑新树有什么特点:

  • 树高为 \(\log n\),即点分治递归层数。

  • 两个点 \(l, r\) 在点分树上的 \(\operatorname{LCA}\),必然在原树中 \(l, r\) 的路径上。

笛卡尔树

类似与 treap,我们对一个序列建树,对于原序列的编号满足二叉搜索树关系,对于权值满足大/小根堆方式,注意若权值相等,则将序号最小为父亲节点。

一个简单的建树方法,找到当前区间最大/小值,分治去做,找最大值用 ST 表,复杂度 \(O(n\log n)\),有更优秀的建树方式,但没必要学。

因为这玩意都是用来转化的。

比较经典的例题:

UOJ424:映射到笛卡尔树上后,区间 \([l, r]\) 的最大值的点就是笛卡尔树上 \(l,r\)\(\operatorname{LCA}\),那么其本质就是问有多少不同构的笛卡尔树。

进一步考虑怎么把 \(1-m\) 全部包含,想到笛卡尔树的性质是父亲一定严格大于左儿子(相当于值域递减),于是满足 \(1-m\) 包含的转化条件就是这棵笛卡尔树的最长链长度不超过 \(m-1\),设 \(f_{i, m}\) 为满足 \(i\) 个点、最长链不超过 \(m-1\) 的笛卡尔树的个数:

\[f_{i, 0}=[i=0],f_{i, 1}=1, f_{i, m}=\sum_{j=0}^{i-1}f_{j, m-1}f_{i-1-j, m} \]

然后就是我不会的生成函数+多项式了

[NOI2019] 机器人:转化成分别往左儿子和右儿子下走,变成左右链长度在 2 以内,DP 即可

图论分块

设重点为 \(deg_u \ge \sqrt{2m}\) 的点,其余为轻点,由此珂知重点 \(u\) 连向其他重点的边不超过 \(\sqrt{2m}\) 条,因为重点个数不超过 总入度/重点最低度数=\(2m/ \ge \sqrt{2m}=\sqrt{2m}\)

对于重点修改操作,直接修改重点和与其相连的重点,

对于轻点修改操作,直接暴力修改全部相邻点。

对于重点询问,直接输出维护好的信息。

对于轻点询问,暴力枚举加上重点给它的贡献。

CDQ 分治

线段树分治

网络流

多项式

万能欧几里得

二进制分组

一种强有力的在线转亚离线的思想,对于带(末尾)插入/删除的动态 DS 问题,能在时间复杂度多只 \(\log\) 的情况下,使得每次暴力重构能够艹过去,前提是合并复杂度与序列长度成线性,如凸包合并,主席树单点合并。

考虑类似于多重背包的二进制拆分,每当序列新插入一个值的时候,将其直接插入,随后看前面维护的整块 +1 后是否能成为 2 的整次幂,若能则合并,并且一直合并下去。

这样就能保证最终只有 \(\log n\) 块,那么询问分块即可。

删除时把权值改为 -1.

posted @ 2022-07-21 08:28  127_127_127  阅读(47)  评论(2编辑  收藏  举报