树的直径
这是树型结构中的一个重要概念,可以用于解决许多问题。树的直径有两种求法:一是通过2次bfs求得,二是通过树形dp求得。bfs的优点是可以方便地求出直径上的点,dp的优点是代码短且可以求带有负权边的树的直径。
bfs法:
1.以任意一个节点为起点,求出与它最远的节点p。
2.以p为起点,再做一次bfs,求出与它最远的节点q
3.p,q间的路径即为树的直径
证明略复杂,情况较多,只要大致了解并记忆这个做法就可以了。
树形dp法
一个很巧妙的办法:首先很容易考虑树形dp用dx表示从x出发可以走到以x为根的子树中的节点的最远距离,那么不妨设x的子节点为y1,y2,y3,……,yt
显然dx=max(dyi+edge(x,yi)) (i ∈ \in ∈[1,t])。而经过x的最长链长度fx=max(dyi+edge(x,yi)+dyj+edge(x,yj)) (1 ≤ \leq ≤j<i ≤ \leq ≤t)。显然去枚举并不是明智之举,我们发现当dx尚未被更新时,恰好储存的就是
max(dyj+edge(x,yj)) (i ∈ \in ∈[1,i-1])的值,那么显然fx可表示为fx=max(dx+dyi+edge(x,yi)),然后再去更新一下dx就可以了。
总结一下就是:妙用已经维护了的信息。(物尽其用)
例题1:AcWing 350.巡逻
这个题目是一个很好的练习题,考察了两种树的直径的求法。那么初步分析这个题:不妨把地图变成一个以1为根节点的树,那么不加任何一条边的走法实际上就是树的遍历,最小巡逻距离就是2(n-1)。
先考虑K=1的情况:我们要使巡逻的总距离尽量短,就要让中间可以尽量多节省一些距离。那么我们很容易考虑到:加入一条路径后,直接通过它跳过一段路径,直接进行后一段回溯就可以使答案变小了。那么我们就要找到连接两点的最长路径:树的直径。把这条边直接加在树的直径的端点之间即可。那么最终的答案就是2(n-1)-L1+1。(L1为树的直径)
再考虑K=2的情况:接下来的思路毫无疑问是要找另一条直径并把它的长度减去。但是事实真的这么简单吗?实际上这样找的直径两端相连后成的两个环有可能会重叠,那么实际上重叠部分不会被走过,那么这是不合题意的,实际上重叠部分又变回了走两次的状态。那么怎么修改呢?其实很简单:将第一条直径上的所有边权置为-1(这样算答案的时候就又会把次数加回来),然后再求一次树的直径即可,记为L2。那么最终的答案就是2(n-1)-L1+1-L2+1=2n-L1-L2。如果L2是包含负权边的,那么-L2相当于加回了L1中减去的次数。
最近公共祖先(LCA)
求最近公共祖先的两种方法:
倍增DP法
void bfs(int root){
queue<int> q;
q.push(root);
d[root]=1;
while(q.size()){
int t=q.front();
q.pop();
for(int i=head[t];i;i=ne[i]){
int j=to[i];
if(d[j]) continue;
dist[j]=dist[t]+w[i];
d[j