0.说明
本文译自Tarjan的论文:
https://www.cs.princeton.edu/courses/archive/fall03/cs528/handouts/a%20fast%20algorithm%20for%20finding.pdf
选取了其中的一部分,有删改,以原文为准。
1.简介
在学习全局流分析和程序优化时,如下图论问题自然地浮现出来。设G(V,E,r)是一张流程图(本文中可以简单地代之以‘有向图’——译者注),以r为起点。我们称G中的节点v支配了另一个节点w,如果每一条从r到w的路径都包含了v。如果v支配w,并且w的其他支配点均支配v,我们就称v是w的最近支配点(又译最近必经点——译者注),记为v=idom(w)。
定理1. 流程图G=(V,E,r)中,除r外的所有节点均有唯一的最近支配点。边集{(idom(w),w)|w∈V-{r}}形成一棵以r为根的有向树,我们称之为G的支配树。v支配w当且仅当v在支配树中是w的完全祖先(非本身的祖先——译者注)。见图1、2.
图1.一张流程图
图2.图1中的支配树。
我们希望为任意的流程图G构造支配树。如果G代表了某种控制流程或计算机程序,支配树能够帮助我们分辨关于哪些代码修改是安全的。
2.深度优先搜索和支配点
快速支配点算法包含三个部分。首先,对输入的流程图G=(V,E,r)进行从r开始的深度优先搜索,并将图G中节点按照DFS访问顺序从1到n编号。DFS建立了一棵以r为根的生成树T,其节点以先根顺序编号。见图3.
图3.对图1进行深度优先搜索。实线是生成树边,虚线是非树边。括号中的数字是节点编号,字母是半支配点。
为方便陈述, 我们以编号代指点。
如下的“路径引理”是DFS的一个重要性质,在支配点算法的正确性证明中起决定性作用。
引理1. 若v,w是G中节点且v<=w,则任意从v到w的路径必然包含它们在T中的一个公共祖先。
其次,我们对每个节点w≠r计算一个值,称为它的“半支配点”,记作sdom(w),定义如下:
sdom(w)=min{v|有路径v=v0, v1, ..., vk=w使得vi>w对1<=i<=k-1成立}. ···(1)
见图3.
然后,我们使用半支配点计算所有点的最近支配点。
半支配点的一些性质使之成为计算支配点的良好中介。若节点w≠r,则sdom(w)一定是T中w的完全祖先,而idom(w)是sdom(w)的(可能不完全)祖先。如果我们将G中非树边替换为边集{(sdom(w),w)|w∈V且w≠r},则各节点支配关系不变。于是,只要我们知道了生成树T和半支配点,我们就能计算支配点。
在本章节的余下部分中,我们将证明半支配点和最近支配点的一些性质,由它们可以推出算法的合法性。接下来三个引理给出了生成树、半支配点和最近支配点的一些基本关系。
引理2. 对任意节点w≠r,idom(w)+->w.(原注:本文中,记号"x·->y"意味着x是y在DFS生成树T中的祖先,而"x+->y"意味着x·->y且x≠y。)
证明. w的支配点必然在T中从r到w的路径上。
引理3. 对任意节点w≠r,sdom(w)+->w.
证明. 设w在T中的父亲为parent(w).由于(parent(w),w)是G中的一条边,由(1)式,sdom(w)<=parent(w)<w.同样根据(1),有一条路径sdom(w)=v0, v1, ..., vk=w使得vi>w对1<=i<=k-1成立。由引理1,路径上的某节点vi是sdom(w)和w在T中的公共祖先。但必然有vi<=sdom(w).这意味着i=0,即vi=sdom(w),且sdom(w)是w的一个完全祖先。
引理4. 对任意节点w≠r,idom(w)·->sdom(w).
证明. 由引理2,3,idom(w)和sdom(w)都是w的完全祖先。我们在从r到w的路径后面接上(1)式规定的路径sdom(w)=v0, v1, ..., vk=w(其中vi>w对1<=i<=k-1成立),这条从r到w的路径避开了T中既是w的完全祖先,也是sdom(w)的完全后代(不等于它自身的后代——译者注)的那些点。因此idom(w)一定是sdom(w)的祖先。
引理5. 设节点v,w满足v·->w。则v·->idom(w)或者idom(w)·->idom(v).
证明. 设x是idom(w)的任意一个完全后代,且同时是v的完全祖先。则必然有一条从r到v不经过x的路径。将这条路径和从v到w的树上路径连接起来,我们就得到了一条从r到w不经过x的路径。因此idom(w)要么是v的后代,要么是idom(v)的祖先。
使用引理1~5,我们得到了两个有用的结论,它们提供了一种根据半支配点计算最近支配点的方法。
定理2. 设w≠r。假设所有使得sdom(w)+->u·->w的u都满足sdom(u)>=sdom(w),那么idom(w)=sdom(w).
证明. 由引理4,只需要证明sdom(w)支配了w。考虑任意一条从r到w的路径p。设x为这条路径上最后一个满足x<sdom(w)的点。若无此x,则sdom(w)=r一定支配w。否则,设y是路径上x之后,满足sdom(w)·->y·->w的第一个点。设路径q=(x=v0, v1, v2, ..., vk=y)是路径p中从x到y的部分。
我们断言对于1<=i<=k-1有vi>y。否则如果有vi<y,由引理1,某个满足i<=j<=k-1的vj是y的祖先。由于x的选择方式,一定有vj>=sdom(w),这意味着sdom(w)·->vj·->y·->w,这和y的选择矛盾。这证明了上述断言。
这一断言,加上半支配点的定义,得到了sdom(y)<=x<sdom(w).根据定理的假设,y不可能是sdom(w)的完全后代。这意味着y=sdom(w),且sdom(w)位于路径p上。由于路径p是任意的,sdom(w)支配了w。证毕。
定理3. 设w≠r,u是所有满足sdom(w)+->u·->w的节点中sdom(u)最小的那个。则sdom(u)<=sdom(w),而且idom(u)=idom(w).
证明. 设节点z满足sdom(w)->z·->w。则sdom(u)<=sdom(z)<=sdom(w).(译者注:似误?sdom(u)<=sdom(w)是显然的,因为u可以等于w嘛!)
由引理4,idom(w)是sdom(w)的祖先,因此是u的完全祖先。由引理5,idom(w)·->idom(u).为了证明idom(u)=idom(w),只需要证明idom(u)支配了w。
考虑从r到w的任意路径p。设x是路径中最后一个满足x<idom(u)的点。若没有这样的x,那么idom(u)=r一定支配w。否则,设y为路径上x之后第一个满足idom(u)·->y·->w的节点。设路径q=(x=v0, v1, v2, ..., vk=y)是路径p中从x到y的一部分。和定理2中的证明类似,x和y的选择意味着vi>y对1<=i<=k-1成立。因此sdom(y)<=x.由引理4,idom(u)<=sdom(u),所以sdom(y)<=x<idom(u)<=sdom(u).
由于u的选择,y不可能是sdom(w)的完全后代。并且,y不可能既是u的祖先,也是idom(u)的完全后代,因为在此情况下,把从r到sdom(y)的树上路径接上路径sdom(y)=v0, v1, ..., vk=y(其中vi>y对1<=i<=k-1成立),再接上从y到u的树上路径,就形成了一条从r到u且避开idom(u)的路径,这和idom(u)的定义矛盾。
由于idom(u)·->y+->u·->w(译者注:原文‘y’处为‘v’,似误),并且idom(u)·->y·->w,余下的唯一可能就是idom(u)=y.因此idom(u)位于r到w的路径上。由于p是任意的,idom(u)必然支配w。证毕。
推论1. 设w≠r,u是所有满足sdom(w)+->u·->w的节点中sdom(u)最小者。那么,
idom(w)= sdom(w)(若sdom(w)=sdom(u)),或者idom(u)(其他状况)。 ···(2)
证明. 由定理2,3,这是显然的。
下面的定理给出了一种计算半支配点的方式。
定理4. 对任意节点w≠r,
sdom(w)=min({v|(v,w)∈E且v<w}∪{sdom(u)|u>w且存在边(v,w)满足u·->v}) ···(3)
证明. 设x等于(3)式右端。我们首先证明sdom(w)<=x。如果x使得(x,w)∈E且x<w,那么根据(1),sdom(w)<=x。在另一种情况下,x=sdom(u),其中u>w,且存在边(v,w)满足u·->v。由(1),存在一条路径x=v0, v1, ..., vj=u,使得vi>u>w对于1<=i<=j-1成立。而树上路径u=vj->vj+1->...->vk-1=v满足vi>=u>w对j<=i<=k-1成立。因此路径x=v0, v1, ..., vk-1=v, vk=w满足vi>w对1<=i<=k-1成立。由(1),sdom(w)<=x。
我们还需要证明sdom(w)>=x。设简单路径sdom(w)=v0, v1, ..., vk=w满足vi>w对1<=i<=k-1成立。若k=1,那么(sdom(w),w)∈E。而根据引理3,sdom(w)<w。于是sdom(w)>=x。对k>1的另一种情况,设j是使得j>=1并且vj·->vk-1的最小值。这样的j一定存在,因为j可以等于k-1.
我们断言:对于1<=i<=j-1,vi>vj成立。否则,我们选取所有满足1<=i<=j-1且vi<=vj的i中使得vi最小的那个。由引理1,vi·->vj,和j的选择矛盾。这证明了断言。
根据上述断言,sdom(w)>=sdom(vj)>=x。因此无论k=1还是k>1,都有sdom(w)>=x,定理得证。
[CodeChef FEB14]Graph Challenge解题报告(求半支配点)
题意
给一张有向图,使得从1开始按某种顺序DFS,可以让每个点的标号等于其DFS序号。求每个点的半支配点。
http://cojs.tk/cogs/problem/problem.php?pid=2117
题解
使用
Lengauer Tarjan算法,对这一算法的描述和证明见我的上一篇博文:
http://blog.csdn.net/wmdcstdio/article/details/49868575
当然本题只需要求半支配点。
首先按照适当顺序DFS,还原题目描述中所称的DFS生成树。然后直接套算法。
代码如下,思路很简单。要点都写在注释中了。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int SIZEN=100010;
void getint(int &x){
char c=0;
for(;c<'0'||c>'9';c=getchar());
for(x=0;'0'<=c&&c<='9';c=getchar()) x=x*10+c-'0';
}
int N,M,Q;
vector<int> c[SIZEN],ct[SIZEN];
int timer=0,fa[SIZEN];
int ufs[SIZEN],best[SIZEN],sdom[SIZEN];
int find(int x){//将x连接到路径最上端,并计算出一路上诸点的best
if(x==ufs[x]) return x;
int y=find(ufs[x]);
if(sdom[best[ufs[x]]]<sdom[best[x]]){//注意ufs[x]≠y
best[x]=best[ufs[x]];
}
return ufs[x]=y;
}
void Lengauer_Tarjan(void){
for(int i=1;i<=N;i++) ufs[i]=best[i]=sdom[i]=i;
for(int i=N;i>=1;i--){
for(int j=0;j<ct[i].size();j++){
/*对于<i的那些,find之后是它本身
对于>i的那些,find之后得到了它到根的最优sdom
注意,对于第二种情况,u~v这一路上必然全大于i*/
int u=ct[i][j];
find(u);
sdom[i]=min(sdom[i],sdom[best[u]]);
}
ufs[i]=fa[i];
}
}
bool vis[SIZEN];
void DFS(int x){
vis[x]=true;
timer++;
for(int i=0;i<c[x].size();i++){
int u=c[x][i];
if(!vis[u]&&u==timer+1){
fa[u]=x;
DFS(u);
}
}
}
int ans[SIZEN];
void work(void){
timer=0;
memset(vis,0,sizeof(vis));
fa[1]=0;
DFS(1);
Lengauer_Tarjan();
memset(ans,0,sizeof(ans));
for(int i=2;i<=N;i++) ans[sdom[i]]++;
int x;
for(int i=1;i<=Q;i++){
getint(x);
printf("%d ",ans[x]);
}
printf("\n");
}
void read(void){
getint(N);getint(M);getint(Q);
for(int i=1;i<=N;i++) c[i].clear(),ct[i].clear();
int a,b;
for(int i=1;i<=M;i++){
getint(a);
getint(b);
c[a].push_back(b);
ct[b].push_back(a);
}
for(int i=1;i<=N;i++) sort(c[i].begin(),c[i].end());
}
int main(void){
//freopen("input.in","r",stdin);
int T;getint(T);
while(T--){
read();
work();
}
return 0;
}