【dominator tree】 Lengauer-Tarjan algorithm

来源:互联网 发布:算法 人民邮电出版社 编辑:程序博客网 时间:2024/05/22 06:04

hdu4694

题意:给定源点,求出源点到其他各点的关键点

 Lengauer-Tarjan algorithm
按理说这也是个经典算法,跟lca的tarjan和强连通的tarjan都有极其相似之处,但是貌似并没有推广
感觉出题比较好出,先求个dominator tree,然后再在上面各种搞,虽然多半会出成一个拼接题...
首先有几个链接
一些概念
http://en.wikipedia.org/wiki/Dominator_(graph_theory)#Algorithms
http://help.eclipse.org/juno/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fdominatortree.html
详细图解以及算法流程
http://www.cl.cam.ac.uk/~mr10/lengtarj.pdf
最后是中文详解
http://www.chnxp.com.cn/soft/down-17744.html 有一节是专门解释这个算法的


首先定义几个概念

idom 最近必经点 代表的是距离节点i最近的一个必经点,明显idom是唯一存在的
dominator tree 这棵树上的每一个节点都是其儿子的idom
明显我们求出这棵树之后,原题要求的就是每个节点到根的路径上的各节点的编号和
然后辅助定义

semi 半必经点,表示的是在dfs树上,节点i的祖先中,可以通过一系列的非树边走到i的,深度最小的祖先,i的直系父亲也可以是半必经点


然后有半必经点定理
考虑节点i的前驱j
1、如果dfn[j]<dfn[i],则j明显是i的一个祖先,那么j是semi[i]的一个候选之一
2、dfn[j]>dfn[i],那么j的祖先u(包括j),semi[u]都是semi[i]的候选
在这二者中取dfn最小的,就是semi[i]

根据semi,我们可以求出idom
必经点定理
在semi[i]~i的一条链上(包括i,不包括semi[i]),找到一个节点y,使得dfn[semi[y]]最小,那么如果
1、semi[y]==semi[i],则idom[i]=semi[i];
2、semi[y]!=semi[i],则idom[i]=idom[y];

有了这两个定理,我们不难得出一个o(n^2)的暴力算法,但是Lengauer-Tarjan却给出了o(nlogn)的算法
我没有仔细去看这个算法的伪代码,大致看了一下这个算法的思想,感觉还是运用了并查集,并且有求lca的既视感,当然其运用到dfn的特征,又无疑是来自求强连通分量的
按照dfn从大到小的顺序处理每个节点,可以根据半必经点定理可以求出semi,这里我有点疑惑的是,此时dfn小于i的节点的semi还没求出来会不会产生影响,因为我发现实现的时候貌似是忽略了dfn比当前节点小的节点;其中有一步是查询其祖先的最小semi,这个可以用一个并查集实现,用best表示当前节点向上找到的dfn[semi[]]最小的节点,我们考虑每个子树都是儿子比祖先先处理,那么每次处理完一个节点的时候,把其子树都并起来,查询的时候通过路径压缩就可以快速查询了;然后是利用必经点定理,我们把semi[j]==i的节点j都链在i上,都处理完i的时候,i~j之间的semi都处理完了(是不是像lca),此时就可以开始求dfn[semi[y]]最小的y了,这个也可以用刚才的并查集实现,注意此时并不能直接求出idom,因为y的idom可能还没求出,要等到最后再按dfn从小到大扫一遍才可以求出来

我的实现与链接给出的伪代码应该有一些差别,但思想应该差不多

关于忽略dfn比当前小的节点,在主代码手的提示下,感觉是因为两点的lca之上的节点的semi都是不需要考虑的

updata2014.4.7 原创题一道 http://acm.sjtu.edu.cn/OnlineJudge/problem/1251

updata2014.4.9 这个拓展问题也比较有趣 http://cstheory.stackexchange.com/questions/17509/multiple-sources-dominator-trees-compact-representation-and-fast-algorithm

#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>#include <algorithm>const int oo=1073741819;using namespace std;int tail[4][200000];int next[4][2000000],sora[4][2000000];int ss[4],top,w_time,n,m;int rel[200000],semi[200000],b[200000],idom[200000],best[200000],st[200000],pre[200000];int ans[200000];void origin(){for (int e=0;e<=3;e++) ss[e]=n;for (int i=1;i<=n;i++) {for (int e=0;e<=3;e++)tail[e][i]=i,next[e][i]=0;rel[i]=0;semi[i]=idom[i]=pre[i]=0,best[i]=i;b[i]=i;ans[i]=0;}rel[0]=oo;}void link(int e,int x,int y){++ss[e],next[e][tail[e][x]]=ss[e],tail[e][x]=ss[e],sora[e][ss[e]]=y,next[e][ss[e]]=0;}void dfs(int x,int y){++w_time,rel[x]=w_time;st[++top]=x,pre[x]=y;for (int i=x,ne;next[0][i];) {i=next[0][i],ne=sora[0][i];if (!rel[ne]) dfs(ne,x);}}int find(int x){int y=b[x];if (b[x]!=x) b[x]=find(b[x]);if (rel[semi[best[y]]]<rel[semi[best[x]]])best[x]=best[y];return b[x];}void getans(int x,int sum){ans[x]=sum+x;for (int i=x,ne;next[3][i];) {i=next[3][i],ne=sora[3][i];getans(ne,sum+x);}}int main(){freopen("input.txt","r",stdin);freopen("output.txt","w",stdout);for (;scanf("%d%d",&n,&m)==2;) {origin();for (int i=1;i<=m;i++) {int x,y;scanf("%d%d",&x,&y);link(0,x,y);link(1,y,x);}w_time=0;top=0;dfs(n,0);for (int i=top;i>=1;i--) {int ne=st[i];for (int j=ne,na;next[1][j];) {j=next[1][j],na=sora[1][j];if (!rel[na]) continue;if (rel[na]>rel[ne]) {find(na);int y=semi[best[na]];if (rel[y]<rel[semi[ne]]) semi[ne]=y;}else {int y=na;if (rel[y]<rel[semi[ne]]) semi[ne]=y;}}if (ne!=n) link(2,semi[ne],ne);for (int j=ne,na;next[2][j];) {j=next[2][j],na=sora[2][j];find(na);int y=best[na];if (semi[y]==semi[na]) idom[na]=semi[na];else idom[na]=y;}for (int j=ne,na;next[0][j];) {j=next[0][j],na=sora[0][j];if (pre[na]==ne) {na=find(na);b[na]=ne;}}}for (int i=2;i<=top;i++) {int ne=st[i];if (idom[ne]!=semi[ne]) idom[ne]=idom[idom[ne]];link(3,idom[ne],ne);} getans(n,0);for (int i=1;i<=n-1;i++) printf("%d ",ans[i]);printf("%d\n",ans[n]);//for (int i=1;i<=n;i++) cout<<semi[i]<<' ';cout<<endl;}return 0;}

spoj59

求有向图的割点

#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>#include <algorithm>const int oo=1073741819;using namespace std;int ss[3],tail[3][5050],next[3][210000],sora[3][210000];int rel[5050],semi[5050],idom[5050],cnt[5050],pre[5050];int w_time,b[5050],best[5050],st[5050];int top,n,m;void origin(){for (int e=0;e<=2;e++) ss[e]=n;for (int i=1;i<=n;i++) {for (int e=0;e<=2;e++) tail[e][i]=i,next[e][i]=0;rel[i]=semi[i]=idom[i]=cnt[i]=0;b[i]=best[i]=i;}}void link(int e,int x,int y){++ss[e],next[e][tail[e][x]]=ss[e],tail[e][x]=ss[e],sora[e][ss[e]]=y,next[e][ss[e]]=0;}void dfs(int x,int y){++w_time,rel[x]=w_time;st[++top]=x,pre[x]=y;for (int i=x,ne;next[0][i];) {i=next[0][i],ne=sora[0][i];if (!rel[ne]) dfs(ne,x);}}int find(int x){int y=b[x];if (b[x]!=x) b[x]=find(b[x]);if (rel[semi[best[y]]]<rel[semi[best[x]]])best[x]=best[y];return b[x];}int main(){freopen("input.txt","r",stdin);//freopen("output.txt","w",stdout);for (;scanf("%d%d",&n,&m)==2;) {origin();for (int i=1;i<=m;i++) {int x,y;scanf("%d%d",&x,&y);link(0,x,y);link(1,y,x);}w_time=top=0;dfs(1,0);rel[0]=oo;for (int i=top;i>=1;i--) {int ne=st[i],na;for (int i=ne;next[1][i];) {i=next[1][i],na=sora[1][i];if (!rel[na]) continue;if (rel[na]>rel[ne]) find(na),na=semi[best[na]];if (rel[na]<rel[semi[ne]]) semi[ne]=na;}if (ne!=1) link(2,semi[ne],ne);for (int i=ne;next[2][i];) {i=next[2][i],na=sora[2][i];find(na);if (semi[best[na]]==ne) idom[na]=ne;else idom[na]=best[na];}for (int i=ne;next[0][i];) {i=next[0][i],na=sora[0][i];if (pre[na]==ne) b[find(na)]=ne;}}for (int i=2;i<=top;i++) {int ne=st[i];if (idom[ne]!=semi[ne]) idom[ne]=idom[idom[ne]];cnt[idom[ne]]++;}int ans=0;for (int i=1;i<=n;i++)if (cnt[i]) ans++;printf("%d\n",ans);for (int i=1;i<=n;i++)if (cnt[i]) {ans--; if (ans) printf("%d ",i);else printf("%d\n",i);}}return 0;} 


原创粉丝点击