感觉天天做题智商并没有提高,于是颓废之余瞎学点东西。
不保证对除我自己之外的任何人易懂。
0.一些定义
为了方便叙述,以下涉及到节点大小比较的地方无特殊说明则直接用节点编号代替 ( ext{dfn})(DFS 树上的时间戳)。
在一个从给定起点出发能到达任意一个点的有向图上,一个点 (i) 支配点 (j) 当且仅当删去 (i) 后不存在起点到 (j) 的路径,显然一个点的支配点不一定只有一个。
方便起见,在 DFS 树上我们采用 Tarjan 的一些表示方法:
(u o v) 表示 (u) 直接连到 (v);
(uxrightarrow{.}v) 表示 (u) 是 (v) 的祖先;
(uxrightarrow{+}v) 表示 (uxrightarrow{.}vland u
e v)。
以下罗列的一些引理或定理一般都可以直接一步反证出来,所以略去了绝大部分证明,如果难以理解可以自己画画图(我就是这么做的)。
另外,无特殊说明时默认节点不是起点。
定义节点 (u) 的直接支配点 ( ext{idom}_u) 为支配 (u) 的点中 ( ext{dfn}) 最大的点。
引理一:根据定义显然 ( ext{idom}_uxrightarrow{+}u)。
定义节点 (u) 的半支配点 ( ext{sdom}_u) 为最小的节点 (v) 满足原图中存在一条 (v) 到 (u) 的路径,路径上每个点(除 (v))都大于 (u)。
引理二:显然 ( ext{sdom}_uxrightarrow{+}u)。
1.一些有用的结论
引理三:显然 ( ext{idom}_uxrightarrow{.} ext{sdom}_u)。
引理四:(forall uxrightarrow{.}v,uxrightarrow{.} ext{idom}_vlor ext{idom}_vxrightarrow{.} ext{idom}_u)。
有了这些引理,我们就可以导出一些有用的定理。
定理一:若(forall v ext{ s.t. } ext{sdom}_uxrightarrow{+}vxrightarrow{.}u, ext{sdom}_vge ext{sdom}_u),则 ( ext{idom}_u= ext{sdom}_v)。
可以性感地理解为所有 (v) 都被封闭在 ( ext{sdom}_u) 到 (u) 里面了,找不出一个 (v) 使得删去 ( ext{sdom}_u) 后还能从上面走到 (v),也就是 ( ext{sdom}_u) 支配 (u)。
定理二:若(exists v ext{ s.t. } ext{sdom}_uxrightarrow{+}vxrightarrow{.}u, ext{sdom}_vle ext{sdom}_u),则 ( ext{sdom}) 最小的点 (p) 满足 ( ext{idom}_p= ext{idom}_u)。
显然 ( ext{idom}_u) 一定在 ( ext{sdom}_p) 上面,而上面最大的点就是 ( ext{idom}_p)。
然后我们把上面两个揉起来就得到推论:
推论一:设满足 ( ext{sdom}_uxrightarrow{+}vxrightarrow{.}u) 的点中 ( ext{sdom}) 最小的 (v) 为 (p),那么有
所以 ( ext{idom}) 可以通过 ( ext{sdom}) 求出来,那么 ( ext{sdom}) 怎么求呢?我们有以下解法:
找到所有原图中连向 (u) 的 (v),所有 (<u) 的 (v) 和 (>u) 的 (v) 的 (>u) 的祖先的半支配点都可能是 (u) 的半支配点(主语是 (<u) 的 (v) 和半支配点)。性感理解发现它显然是充分的。
通过这些东西理论上就可以完整地求出直接支配点了,塔老师意料之中地又一次给出了一个神奇的实现:
对于半支配点,我们倒序枚举,由于需要考虑祖先,我们自然想到用并查集维护,带上一个最小半支配点的权值。
然后我们建出半支配树后按照推论一直接计算直接支配点即可,这一步可以利用求半支配点时留下的信息。
2.代码实现
洛谷 P5180 【模板】支配树
实现的时候需要注意一个细节:处理直接支配点时枚举完父亲要清掉父亲。
const int N=200010,M=300010;
int n,m;
struct Graph {
struct Edge {
int to,nxt;
}e[M];
int hd[N],cnt;
il void ade(int u,int v){
e[++cnt].to=v,e[cnt].nxt=hd[u],hd[u]=cnt;
}
Graph(){
memset(hd,0,sizeof hd),cnt=0;
}
}G,R,D;//原图、反图、半支配树
int dfn[N],idx[N],fa[N],tim;
void DFS(int u,int ff){
idx[dfn[u]=++tim]=u,fa[u]=ff;
for(rg int i=G.hd[u];i;i=G.e[i].nxt){
int v=G.e[i].to;
if(!dfn[v])DFS(v,u);
}
}
//直接支配/半支配/父亲/权值
int idm[N],sdm[N],f[N],mn[N];
int F(int k){//并查集维护最小sdom
if(f[k]==k)return k;
int ff=F(f[k]);
if(dfn[sdm[mn[f[k]]]]<dfn[sdm[mn[k]]])mn[k]=mn[f[k]];
return f[k]=ff;
}
void Tarjan(){
for(rg int i=1;i<=n;i++)f[i]=mn[i]=sdm[i]=i;
for(rg int i=tim;i>1;i--){
int u=idx[i];
for(rg int i=R.hd[u];i;i=R.e[i].nxt){
int v=R.e[i].to;
if(!dfn[v])continue;
F(v);
if(dfn[sdm[mn[v]]]<dfn[sdm[u]])sdm[u]=sdm[mn[v]];
}
D.ade(sdm[u],u),f[u]=fa[u];
for(rg int i=D.hd[fa[u]];i;i=D.e[i].nxt){
int v=D.e[i].to;F(v);//运用推论一求idom
idm[v]=sdm[mn[v]]==fa[u]?fa[u]:mn[v];
}
D.hd[fa[u]]=0;
}
for(rg int i=2;i<=tim;i++){
int u=idx[i];//完善推论一的第二种情况
if(idm[u]!=sdm[u])idm[u]=idm[idm[u]];
}
}
int siz[N];
int main(){
Read(n),Read(m);
for(rg int i=1,u,v;i<=m;i++){
Read(u),Read(v),G.ade(u,v),R.ade(v,u);
}
DFS(1,0),Tarjan();
for(rg int i=tim;i;i--){
int u=idx[i];//统计答案
siz[idm[u]]+=++siz[u];
}
for(rg int i=1;i<=n;i++)printf("%d ",siz[i]);
puts("");
KafuuChino HotoKokoa
}
3.简单应用
省选联考 2021 A 支配。
考虑加入 ((u,v)) 之后满足哪些条件的点会发生变化(显然受支配集的变化一定是减少)。
发生变化也就是某个祖先不再支配它,而一个点的受支配集变化会引起整个子树变化,所以我们只需要考虑父亲不再支配它的点。
这样的点 (x) 一定满足 (1
ightsquigarrow u o v
ightsquigarrow x) 且不经过 (x) 的父亲。
显然是不能每次暴力求的,我们可以 (O(n^2)) 对于每个点预处理出不经过它的父亲就能到达它的所有点,复杂度已经足以通过本题。
struct DominatorTree {
struct Edge {
int to,nxt;
}e[N];
int hd[N],cnt;
il void ade(int u,int v){
e[++cnt].to=v,e[cnt].nxt=hd[u],hd[u]=cnt;
}
int dfn[N],tim,siz[N];
void DFS(int u,int ff){
dfn[u]=++tim,siz[u]=1;
for(rg int i=hd[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=ff)DFS(v,u),siz[u]+=siz[v];
}
}
}T;
bool vis[N];
void DFSForbid(int u,int ban){
vis[u]=1;
for(rg int i=R.hd[u];i;i=R.e[i].nxt){
int v=R.e[i].to;
if(!vis[v]&&v!=ban)DFSForbid(v,ban);
}
}
bool ok[N][N];//whether i can reach j without Xing fa[j]
void Init(){
for(rg int i=2;i<=n;i++){
memset(vis,0,sizeof vis);
DFSForbid(i,idm[i]);
for(rg int j=1;j<=n;j++)ok[j][i]=vis[j];
}
}
bool isf[N];int d[N];
int Query(int u,int v){
memset(isf,0,sizeof isf),memset(d,0,sizeof d);
for(rg int i=u;i;i=idm[i])isf[i]=1;
for(rg int i=2;i<=n;i++){
if(!isf[idm[i]]&&ok[v][i]){
d[T.dfn[i]]++,d[T.dfn[i]+T.siz[i]]--;
}
}
int res=0;
for(rg int i=1;i<=n;i++)res+=(d[i]+=d[i-1])!=0;
return res;
}
int main(){
Read(n),Read(m),Read(q);
for(rg int i=1,u,v;i<=m;i++){
Read(u),Read(v),G.ade(u,v),R.ade(v,u);
}
Tarjan();
for(rg int i=2;i<=n;i++)T.ade(idm[i],i);
Init(),T.DFS(1,0);
for(rg int i=1,u,v;i<=q;i++){
Read(u),Read(v);
cout<<Query(u,v)<<endl;
}
KafuuChino HotoKokoa
}
可以使用 (O(n^2)) 暴力求支配树也可以使用 Tarjan 的算法,我为了练习选择了后者。然后就因为计算 ( ext{idom}) 时写反 (u,v) 调了一下午。