// 假设图中有 n 个结点, 起始点 s = 1std::bitset<N> vis;std::vector<int> edge[N];std::vector<int> dom[N];void dfs(int u, int del) { vis[u] = true; for (int v : edge[u]) { if (v == del or vis[v]) { continue; } dfs(v, del); }}void getdom() { for (int i = 2; i <= n; ++i) { vis.reset(); dfs(1, i); for (int j = 1; j <= n; ++j) { if (!vis[j]) { dom[j].push_back(i); } } }}
std::bitset<N> dom[N];std::vector<int> Dom[N];int idom[N];void getidom() { for (int u = 2; u <= n; ++u) { for (int v : Dom[u]) { std::bitset<N> tmp = (dom[v] & dom[u]) ^ dom[u]; if (tmp.count() == 1 and tmp[u]) { idom[u] = v; break; } } } for (int u = 2; u <= n; ++u) { e[idom[u]].push_back(u); }}
树上的特例
显然树型图的支配树就是它本身。
DAG 上的特例
我们发现 DAG 有一个很好的性质:根据拓扑序求解,先求得的解不会对后续的解产生影响。我们可以利用这个特点快速求得 DAG 的支配树。
提醒
值得注意的是此处的 DAG 只能有一个起点,如果有多个起点,受起点支配的点在支配树上出现有多个父亲的情况,从而使支配关系不能简单的用支配树来表达。
引理 6: 在有向图上,vdomu 当且仅当 ∀w∈pre(u),vdomw。
证明: 首先来证明充分性。考虑任意一条从 s 到 u 的路径都一定经过一个结点 w∈pre(u),而 v 支配这个结点,因此任意一条从 s 到 u 的路径都一定经过 v,因此我们得到 vdomu。
然后是必要性。如果 ∃w∈pre(u),v 不支配 w,则一定有一条不经过 v 的路径 s→⋯→w→⋯→u,因此 v 不支配 u。
我们发现,u 的支配点一定是其所有前驱结点在支配树上的公共祖先,那么显然 u 的直接支配点是所有前驱结点在支配树上的 LCA。考虑倍增求解 LCA 可以支持每次添加一个结点,上述算法显然是可行的。
下面给出参考实现:
std::stack<int> sta;std::vector<int> e[N], g[N], tree[N]; // g 是原图的反图,tree 是支配树int n, s, in[N], tpn[N], dep[N], idom[N]; // n 为总点数,s 为起始点,in 为入度int fth[N][17];void topo(int s) { sta.push(s); while (!sta.empty()) { int u = sta.top(); sta.pop(); tpn[++tot] = u; for (int v : e[u]) { --in[v]; if (!in[v]) { sta.push(v); } } }}int lca(int u, int v) { if (dep[u] < dep[v]) { std::swap(u, v); } for (int i = 15; i >= 0; --i) { if (dep[fth[u][i]] >= dep[v]) { u = fth[u][i]; } } if (u == v) { return u; } for (int i = 15; i >= 0; --i) { if (fth[u][i] != fth[v][i]) { u = fth[u][i]; v = fth[v][i]; } } return fth[u][0];}void build() { topo(s); for (int i = 1; i <= n; ++i) for (int j = 0; j <= 15; ++j) fth[i][j] = s; for (int i = 1; i <= n; ++i) { int u = tpn[i]; if (g[u].size()) { int v = g[u][0]; for (int j = 1, q = g[u].size(); j < q; ++j) { v = lca(v, g[u][j]); } tree[v].push_back(u); fth[u][0] = v; dep[u] = dep[v] + 1; for (int i = 1; i <= 15; ++i) { fth[u][i] = fth[fth[u][i - 1]][i - 1]; } } }}
考虑任意一条 s 到 u 的路径 P,我们需要证明 sdom(u) 一定在 P 中。令 v 为 P 中最后一个满足 v<sdom(u) 的节点。如果 v 不存在则必有 sdom(u)=idom(u)=s,否则令 w 是 P 中 v 之后在 DFS 树中从 sdom(u) 到 u 的路径上的第一个点。
我们接下来证明 sdom(w)≤v<sdom(v)。考虑 T 上 v 到 w 的路径 v=v0→…vk=w,若不成立,则存在 i∈[1,k−1],vi<w。此时一定存在某个 j∈[i,k−1] 满足 vj 是 w 的祖先。由 v 的取值可知 sdom(u)≤vj,于是 vj 也在 DFS 树中从 sdom(u) 到 u 的路径上,与 w 的定义矛盾,因此 sdom(w)≤v<sdom(v),结合定理的条件有 y=sdom(u),即路径 P 包含 sdom(u)。
定理 3: 对于任意节点 u,T 上从 sdom(u) 到 u 的路径上的所有节点中半支配点最小的节点 v 一定满足 sdom(v)≤sdom(u) 和 idom(v)=idom(u)。
证明: 考虑到 u 本身也满足 v 的条件,因此 sdom(v)≤sdom(u)。
由于 idom(u) 是 v 在 T 上的祖先,由引理 11 可知 idom(u) 也是 idom(v) 的祖先,因此只需证明 idom(v) 支配 u。
考虑任意一条 s 到 u 的路径 P,我们需要证明 sdom(u) 一定在 P 中。令 x 为 P 中最后一个满足 x<sdom(u) 的节点。如果 x 不存在则必有 sdom(u)=idom(u)=s,否则令 y 是 P 中 x 之后在 DFS 树中从 sdom(u) 到 u 的路径上的第一个点。
与定理 2 的证明过程同理,我们可以得到 sdom(y)≤x。根据引理 10 有 sdom(y)≤x<idom(v)≤sdom(v)。至此,由 v 的定义可知 y 不能是 sdom(u) 的后代;另一方面,y 不能既是 idom(v) 的后代也是 v 的祖先,否则沿 DFS 树从 s 到 sdom(y) 再沿 P 走到 y,最后沿 DFS 树走到 v 的这条路径不经过 idom(v),与支配点的定义矛盾。因此 y=idom(v),即 P 包含 idom(v)。
根据以上两个定理我们能够得到 sdom(u) 与 idom(u) 之间的关系。
令 v 是满足 v 在 sdom(u) 与 u 之间的结点的所有节点中,sdom(v) 最小的一个节点,那么: