基环树处理方法
无向基环树找环,遍历模板:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int n;
vector<int> e[500005];
bool vis[500005] = {}, stk[500005] = {}, cir[500005] = {};
//stk是搜索栈,作用是避免在找环时从up找到了一条去dn的边,导致up以为自己的nxt是dn
int up, dn, nxt[500005], lvl[500005];
void fnd(int x, int pr, int lv) {
/*
找到环最下面的点之后,令 cir[dn]=true
之后只要自己的子节点有 cir=true 的,则自己的 cir=true,如果子节点是up例外
*/
vis[x] = stk[x] = true;
lvl[x] = lv;
for (int i = 0; i < e[x].size(); i++)
if (!vis[e[x][i]]) {
fnd(e[x][i], x, lv + 1);
if (cir[e[x][i]] && e[x][i] != up)
cir[x] = true, nxt[x] = e[x][i];
}
else if (e[x][i] != pr && stk[e[x][i]])
dn = x, up = e[x][i], cir[x] = true, nxt[x] = e[x][i];
stk[x] = false;
}
int main()
{
cin >> n;
for (int i = 1, u, v; i <= n; i++) {
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
return 0;
}
法一:环套树。
把基环树看作一个环上吊了几棵树,在处理时遍历环上每个点,处理出每棵树的答案,然后做环形的操作。
缺点:只能处理基环树,如果是仙人掌就不适用了。
例子:城市环路
法二:树回边。
以深搜树的方式看待,用处理树的方式(比如树形 DP)。在遇到环上深度最浅的结点的时候,让它把下方的环的结果当作一颗子树汇报给父节点。
这样就可以处理仙人掌了。
这种方式把树的结构视为重点,只是多了几条回边。
翻译:求出基环森林中,每颗基环树的直径之和。(带权)
考虑一颗基环树怎么求直径。
先用深搜树找环,记那个环在深搜树中深度最浅/深的结点为 \(up/dw\),那条回边为 \(bk\),深搜树中结点 \(x\) 的带权深度为 \(lvl[x]\),一个环上的点 \(x\) 在深搜树中在环上的儿子为 \(cs[x]\)。
直径分为两类:一类过 \(bk\),一类不过 \(bk\)。不过 \(bk\) 的一类就是树的直径,很简单。
对于过 \(bk\),必然可以看作 \(bk\) + \(up,dw\) 拓展出去的两条链 的形式。假设 \(up\) 在环上走到结点 \(x\) 然后离开了环,\(dw\) 在 \(y\) 离开了环。
\(x\) 和 \(y\) 之后的命运就已经是树的直径了,在上面已经处理过。唯一不同的就是统计环上的边。\(up\rightarrow x\) 的长度是 \(lvl[x]-lvl[up]\),\(dw\rightarrow y\) 的长度是 \(lvl[dw]-lvl[y]\)。
记 \(dp1[x]=lvl[x]-lvl[up]+\text{x 脱离环后的最长路径},dp2[x]=lvl[dw]-lvl[x]+\text{x 脱离环后的最长路径}\)。同时类似前缀和优化,因为环在深搜树上可以看作一条链 + \(bk\),所以额外算一个 \(mx[x]\) 表示 \(\max(dp2[x],dp2[cs[x]],dp2[cs[cs[x]]],\dots,dp2[dw])\)。
于是可以枚举 \(x\) 的位置,用 \(bk+dp1[x]+mx[cs[x]]\) 快速求出 \(up\) 从 \(x\) 离开环的最大路径长度。
但是还有一种情况没考虑:若 \(x=up\),即 \(up\) 直接从原地离开环,并且往祖先方向走。我们的 \(dp1,dp2,mx\) 都是以 "子树" 来描述的,不包括往祖先走的情况。(虽然不考虑这种情况疑似还有 80)
法一:用类似换根 DP 的方法,单独处理出 \(up\) 向上的长度。但是这违背了我们的初衷:还是把环上的点特殊化了,而且如果要拓展到仙人掌,就要对每个环上最浅的点都做一遍,很麻烦。虽然也是正确的,但是这里不予采用。
法二:仍沿用树形 DP 的思路。\(up\) 向上走的路径,负责结点不是 \(up\),而是这条路径上最浅的结点(LCA)。\(up\) 只需要向上汇报在 \(up\) 的 “子树”(包括 \(bk\))中的向下最长路径即可。
那么 \(up\) 向下的最长路径是多长呢?如果不过 \(bk\),就是 \(dp1[up]\);否则,就是 \(bk+\max(dp2[cs[up]],dp2[cs[cs[up]]],\dots,dp2[dw])=bk+mx[cs[up]]\),注意这里不包括 \(dp2[up]\),否则从 \(dw\) 上到 \(up\) 路径会和 \(bk\) 组成一个环,不再是简单路径。这样就求出了 \(up\) 子树的答案,并且对于更多的简单环,这个方法都同样适用。