【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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章