P5180【模板】支配树
来咯,我们来说说一般图上的支配树。
(前排提醒:本文实质是对老师讲的内容的补充,阅读本文前应该知道 \(\operatorname{idom}\) 及其在 DAG 上的求解方法,具体可以去查看 ZJOI2012 灾难 的题解。)
常见的做法是 Languaer-Tarjan 算法,该算法的核心在于提出了半支配点 \(\text{sdom}\)。
半支配点 \(\operatorname{sdom}\) 的定义是除起点和终点外只经过 DFS 序大于终点的点的起点中 DFS 序最小的一个。
形式化说,将点按照 DFS 序编号后,\(\operatorname{sdom }x=\min\{v\mid\exist v=v_0\rightarrow v_1\rightarrow\cdots\rightarrow v_k=x,\forall 1\le i\lt k,v_i\gt x\}\)。
(渲染问题,\(\exist\) 是存在的意思,懒得改了将就看。)
这里在本文中额外定义 半支配 关系,即满足上述条件,但不一定是最小值的那些 \(v\) 都是 半支配 \(x\) 的,当然 \(\operatorname{sdom }x\) 也 半支配 \(x\)。
这东西看上去屁点性质没有,先不说怎么拿它求答案,根本就求不出来吧?
先暴力考虑怎么求解 \(\operatorname{sdom}\) 吧,下面点的编号即为其 DFS 序。
对于一个点 \(u\) 进行求解,则考虑其前驱 \(v\),分类讨论答案的候选值:
- \(v\lt u\),此时 \(v\) 就直接小于 \(u\) 了,显然只能拿 \(v\) 作为起点,所以 \(v\) 是当前情况唯一可能的候选值,另外其实这种情况只有可能在 \(v\) 是 \(u\) 在 DFS 树上的父亲时成立。
- \(v\gt u\),这个时候 \(v\) 可以作为路径中间的点,于是 \(\operatorname{sdom }v\) 是一个候选值,而此时 \(v\) 可以继续往 DFS 树的父亲走,如果其父亲 \(w\gt u\),这个过程就还能够继续,直到发现 \(w\lt u\) 的时候变成第一种情况为止。
容易发现上面的部分没有漏掉任何一个可能的候选值,这时候做一个暴力从所有候选值中取最小值即可。
发现时间复杂度的瓶颈位于第二种情况,有什么办法优化一下?
考虑按照 DFS 序的逆序 处理。
此时已经处理的点天然满足大于的条件,因此我们可以把第二种情况查询的信息简化为 \(v\) 上面所有已经完成更新的祖先的 \(\operatorname{sdom}\) 的最小值。
注意到维护的信息非常简单,我们可以使用带权并查集简单维护,注意这里不能按秩合并,因为我们的并查集维护的是处理到目前的 DFS 树,形态不能够变。
此时我们能够处理出所有点的 \(\operatorname{sdom}\) 了,时间复杂度为 \(\mathcal{O}(n\log n)\)。
不过就算知道了 \(\operatorname{sdom}\) 又有啥用啊真的是……
我们最终要求的是 \(\operatorname{idom}\) 啊,这个 \(\operatorname{sdom}\) 和它有啥关系嘛……
分析一下,关于 \(\operatorname{idom }x\) 有一个性质,即 \(\operatorname{idom }x\) 一定在 DFS 树上根节点到 \(x\) 的路径上,考虑 \(\operatorname{idom}\) 的定义就可以得到。
而 \(\operatorname{sdom}\) 很显然告诉我们,\(\operatorname{sdom }x\) 子树内的点(不包括它)都不可能成为 \(\operatorname{idom }x\),因为这里存在一条其它的路径不经过它们。
这下 \(\operatorname{sdom}\) 看上去有用了,分析一下怎样求解?
(只说 DAG 做法,另一个我太烂了不会。)
我们往已有的做法上靠,就是 DAG 上的做法,能不能把原图换成一个支配关系相同的 DAG?
要这么做肯定不能虚空建图,我们考虑保留一点原图的元素。
分析一下,我们可以留下 DFS 树,这样子原有的支配关系全部都在,只是多出了一些本来没有的支配关系。
考虑去掉多余的支配关系,设原本不满足支配关系的点对 \(u,v\),其中 \(u\) 不支配 \(v\)。
现在这个点对满足支配关系,我们可以推出 \(u\) 是 \(v\) 的祖先(\(u\) 是 \(v\) 的情况就不讨论了)。
如果 \(u\) 原本不支配 \(v\),则存在一条路径不经过 \(u\) 到达 \(v\),我们分析一下这条路径什么情况。
挂一张图(还没画):
我们可以考虑根节点到 \(v\) 的这条路径,上面必然经过点 \(u\)。
而显然如果要不经过 \(u\) 到 \(v\),必然有两个点 \(x,y\) 满足 \(x\) 能够到达 \(y\) 且 \(x\) 是 \(u\) 的祖先,\(u\) 是 \(y\) 的祖先。
不难发现 \(y\) 一定是通过某条横叉边到达的,由于这是有向图的 DFS 树,所以横叉边一定是由 DFS 序大的点连向小的点的。
容易推出 \(x\) 到 \(y\) 的路径上所有点的 DFS 序均大于 \(y\),所以 \(x\) 半支配 \(y\)。
显然我们连接 \(x,y\) 即可去掉 \(u,v\) 中多余的支配关系,而连接 \(\operatorname{sdom }y,y\) 显然也能够达成一致的效果,因为 \(\operatorname{sdom }x\) 是所有 半支配 \(x\) 的点中深度最浅的那个。
此时没有多余的支配关系了,又因为 \(\operatorname{idom }x\) 是 \(\operatorname{sdom }x\) 的祖先,所以支配关系不会因为这样的操作变少。
不难发现新图是 DAG,于是我们完成了一般图的支配树的求解。
挂个代码吧,没什么参考作用。
#include<bits/stdc++.h>
using namespace std;
constexpr int N=200010;
vector<int>G[2][N],e[N],T[N];
int dfn[N],rev[N],tot,fa[N];
void dfs1(int u)
{
tot++;
dfn[u]=tot;
rev[tot]=u;
for(int v:G[0][u])if(!dfn[v])dfs1(v),fa[v]=u,e[v].push_back(u);
return;
}
int dsu[N],val[N],sdom[N];
pair<int,int>F(int x)
{
if(dsu[x]==x)return make_pair(x,sdom[x]);
pair<int,int>tmp=F(dsu[x]);
val[x]=min(val[x],tmp.second);
dsu[x]=tmp.first;
return make_pair(dsu[x],val[x]);
}
constexpr int MX=15;
int f[N][MX],d[N];
inline int LCA(int u,int v)
{
if(d[u]<d[v])swap(u,v);
for(int i=MX-1;i>=0;i--)if(d[f[u][i]]>=d[v])u=f[u][i];
if(u==v)return u;
for(int i=MX-1;i>=0;i--)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
return f[v][0];
}
int sz[N];
void dfs2(int u)
{
sz[u]=1;
for(int v:T[u])if(!sz[v])dfs2(v),sz[u]+=sz[v];
return;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
G[0][a].push_back(b);
G[1][b].push_back(a);
}
dfs1(1);
iota(dsu+1,dsu+n+1,1);
memset(val,0x7f,sizeof val);
swap(n,tot);
for(int i=n;i>1;i--)
{
int u=rev[i];
sdom[u]=n+1;
for(int v:G[1][u])
{
if(dfn[v]<i)sdom[u]=min(sdom[u],dfn[v]);
else sdom[u]=min(sdom[u],F(v).second);
}
for(int v:G[0][u])if(fa[v]==u)dsu[v]=u,val[v]=sdom[v];
}
for(int i=2;i<=n;i++)sdom[i]=rev[sdom[i]];
for(int i=2;i<=n;i++)e[i].push_back(sdom[i]);
d[1]=1;
for(int i=2;i<=n;i++)
{
int u=rev[i];
int idom=-1;
for(int v:e[u])
{
if(idom==-1)idom=v;
else idom=LCA(idom,v);
}
f[u][0]=idom;
d[u]=d[idom]+1;
for(int i=1;i<MX;i++)f[u][i]=f[f[u][i-1]][i-1];
T[idom].push_back(u);
}
dfs2(1);
printf("%d",n);
for(int i=2;i<=tot;i++)printf(" %d",sz[i]==0?1:sz[i]);
return 0;
}
标签:sdom,支配,int,DFS,一般,operatorname,idom
From: https://www.cnblogs.com/XiaoShanYunPan/p/18209054