【dominator tree】 Lengauer-Tarjan algorithm

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;
} 


发布了165 篇原创文章 · 获赞 10 · 访问量 22万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章