点分治
基础操作
点分治的应用范围非常广泛,条件有:与根的位置无关,一般为链信息统计
话说才知道点分治找重心子树大小要重新统计~
基础的二维偏序,由于具有加减性,把同一子树内的答案容斥掉即可
首先是传统的点分治做法:
由于每个节点分别统计不好实现,于是考虑对颜色进行维护
设 \(cnt[i]\) 表示颜色 \(i\) 出现的路径条数,那么统计 \(\sum cnt\) 解决了子树外的贡献
同时,对于根到当前节点上的颜色,应当产生的贡献为 \(siz-cnt_i\),同时应当考虑对于已经计算过的颜色应当标记而不重算
一个非常巧妙而优秀的做法:
首先对于每种颜色,可以统计为把这种颜色的节点全部删去,那么不同连通块之间都是能产生 \(1\) 的贡献的,那么每个节点的答案应当增加 \(n-\)所在连通块大小
考虑将这一信息保存在每个连通块最浅的节点,遍历时可以方便地取用
由于每个节点只有一个父亲,那么只会存一个信息
当然,直接算会算重,那么应当记录这种颜色上一次的值为多少,进入时更改,退出时还原
对于当前联通块的大小,即为 \(siz-\)子树内这种颜色的大小之和,一遍 \(dfs\) 即可
- 可以发现,无论哪种方法都需要对每种颜色分别处理,这种思想是关键
可以发现这个的棘手之处在于 \(max\) 操作没有加减性,于是需要不断进行结构的合并操作
假设之前子树内的所有路径信息已经维护出来,那么对当前子树的所有路径进入查询
如果路径按照从浅到深加入,那么其余的合法区间也在单调移动
那么此时对于位置来说最优性有保证,可以用单调队列维护
由于接口处答案的统计方式不同,需要对当前颜色,和不同颜色维护两个单调队列
这样发现每棵子树需要把之前深度都遍历一遍,那么把所有子树按照深度排序后来做复杂度就对了
然而目前还没有卡过去常……
另一种做法是把信息统计都线段树上然后进行合并,听着比较难写~
P4183 [USACO18JAN]Cow at Large P
考虑将牛牛初始的位置作为根,那么对于一个子树,在她跑进子树之前完成拦截即可
考虑子树内放置在最浅的叶节点一定最优,这个距离设为 \(dis_u\)
那么一个子树的拦截有效当且仅当 \(dep_u\le dis_u\)
考虑更浅的节点的有效拦截可以直接取代其子树中的所有拦截
那么放置个数=\(\sum [dep_u\le dis_u\&\&dep_{fa_u}>dis_{fa_u}]\)
考虑优化,发现此时表达式不满足了无根性质(即\(fa\))
可以通过一个非常巧妙的构造来消除掉这个影响:
考虑如果将所有满足条件的(不仅仅是恰好最浅的)都产生贡献就可以消除,但是会多出来
为了将所有贡献和设为 \(1\),将每个点的权值设为 \(2-deg_u\),此时子树和为 \(1\)
公式上因为 \(\sum deg_i=2m-1\)(即一条边贡献两个度数,减去最祖先上面的边)
可以理解为节点的贡献都在 \(lca\) 处被消除
此时便可以当做偏序问题用点分治解决了
首先的是暴力的 \(dp\),\(f_u=f_v+P_u(dis_u-dis_v)+Q_u\)
唯一棘手的地方就是转移区间的限制,如果在链上可以使用 \(CDQ\) 分治,在树上就使用点分治
保持树的有根性,对于每个分治中心,先去 \(dp\) 中心以上的部分,那么此时子树的祖先都已经 \(dp\) 完成,那么将子树中的点按照深度排好序并将祖先依次加入候选集合即可
另一个比较妙的方法是维护每个节点的出栈序,然后直接在自己和祖先的区间查询即可,此时恰好其它节点都没有加入(祖先子树要么是之前遍历不在区间中,要么是在区间中但还遍历到)
按重心转移
一类问题是要求选择树中的一个点以满足最值条件,往往可以根据性质的单调性不断跳跃至连通块重心来实现,需要满足价值函数沿着树的某个点的方向单调进行
选择取决于最长路径,所以很显然是单调的
于是先选择重心,去判断最大值在哪个儿子,此时最多只有一个更优,向那个儿子跳跃即可
有性质凸函数之和还是凸函数?
设重心在 \((u,v)\) 这条边上,离 \(u\) 的距离为 \(x\),则总贡献为 \(\sum _{i\notin u} a_i(d_i+x)^{3/2}+\sum_{i\in u}a_i(d_i-x)^{3/2}\)
其中 \(d\) 为距 \(u\) 的距离
此时的大小仍不好比较,那么考虑求导后大于零的 \(v\) 是更优的,向着那个子树移动即可
类似的是幻想乡,只不过带修放在后面吧
边分治
一直以为边分治木有用,再学一边才发现其实用处大得很,只不过又难写又难调,但凡出来都是大毒瘤,写出来的概率实在太低……
先介绍一下过程:
与点分治类似,找到一条边尽量把树分得均匀,然后递归进行
但是边不具有均分性,遇到菊花就完蛋了,于是需要一个三度化的过程,类似于儿子兄弟表示法把整棵树转化为二叉树去做
然后是优缺点:
边分治的应用范围略为狭窄,要求能三度化,即新加入的边不能影响答案,比如数颜色等就不太适用了
除此之外并无缺点(可能难写算一个),其关键在于一个分治中心只对应两个子问题,于是许多 \(log\) 算法的复杂度是适用的,最关键的是边分树是二叉的,允许了出题人进行可持久化、合并等一系列操作……
基础题并不多(好吧是我写的不多),大多数都是点边共用的,就不放了
询问是一个常见形式,即转化为 \(dep\) 和减 \(lca\) 性质,其中 \(lca\) 的部分可以通过经典的到根路径加操作以及到根询问来实现,可以维护出每条边被经过的次数
具体而言,由于边分树上一个点是一条边,原树点都在叶子上,然后枚举其所有祖先,答案就是 \(dis(x,i)+siz* val\),其中表示的是除 \(x\) 以外的子树内
而询问的是一个区间,那么用主席树来维护出区间信息即可
考虑修改操作,直接把第 \(x\) 棵主席树重建即可
点分树
简单而言就是根据选取重心的关系连边建树
具体实现上发现与其父亲距离可以点分治顺便算出,而不用累哼哼写个 \(LCA\) 还跑得慢……
点分树上维护的信息关键都是围绕容斥本棵子树展开的
对于本质,点分树其实是对于树的一种分割方式,但是十分平衡
对于点分树上一个点只要跳父亲就可以得到树上全部的信息
但是一定注意点分树的父子关系非常弱,没有任何加减性!
还是这个比较经典~
设 \(f(i,j)\) 表示 \(i\) 子树内与 \(i\) 距离小于等于 \(j\) 的个数
\(g(i,j)\) 表示 \(i\) 子树内与 \(fa_i\) 距离小于等于 \(j\) 的个数
那么答案就可以统计为 \(f(fa_i,j)-g(i,j)\) 了
对于修改,也直接暴力跳即可
由于平衡性,类似于启发式合并的分析,点分树子树的 \(siz\) 和是 \(log\) 级别的,直接动态开点是可以接受的
同时保存 \(sum\) 和 \(siz\)
那么一个祖先(及对应的边)的贡献就是子树 \(sum+\) 子树 \(siz*\) 边权
这个实际上是刚才说的那个套路,但是带了修改
跳跃子树的过程转化为转移到儿子上
而权值的快速计算和上一道题几乎一样了
个人认为(欢迎指正)如果用边分树可以更一般化,儿子的数量与复杂度就无关了
设 \(S_i=\sum a_v\),考虑往表达式中加入构造
\(ans=\sum S_i^2=sum \sum S_i-\sum S_i(sum-S_i)\)
由于后半部分是定值,成功把二次降为一次